Книга
Книга
ББК 32.973.26-018.2Я75
Д42
УДК 681.3.07
Издательский дом “Вильямс”
Перевод с английского и редакция НМ. Ручко
По общим вопросам обращайтесь в Издательский дом “Вильямс"по адресу: info@williamspublishing.com, http://www.williamspublishing.com
Джордан, Дэвид.
Д42 Обработка объектных баз данных в C++. Программирование по стандарту ODMG. : Пер. с англ. : Уч. пос. — М. : Издательский дом "Вильямс", 2001. — 384 с. : ил. — Парад, тит. англ.
ISBN 5-8459-0149-9 (рус.)
Данная книга является введением в технологию объектных баз данных и предназначена для разработчиков программных продуктов, которые знакомы с объектным проектированием и языком C++ и хотят узнать об объектных базах данных. В книге описаны возможности объектного моделирования, заложенные в среде поддержки объектных баз данных, использующих интерфейсы ODMG C++ и OQL (язык объектных запросов). Здесь рассмотрены различные архитектуры. применяемые объектными базами данных, а также уделено внимание сравнению реляционных и объектно-реляционных баз данных с объектными. Для тех, кто уже использует какую-либо из реализаций стандарта ODMG, книга может послужить справочником по интерфейсу ODMG 2.0 C++.
ББК 32.973.26-018.2я75
Все названия программных продуктов являются зарегистрированными торговыми марками соответствующих фирм.
Никакая часть настоящего издания ни в каких целях не может быть воспроизведена в какой бы то ни было форме и какими бы то ни было средствами, будь то электронные или механические, включая фотокопирование и запись на магнитный носитель, если на это нет письменного разрешения издательства Addison-Wesley Publishing Company, Inc.
Authorized translation from the English language edition published by Addison-Wesley Publishing Company, Inc, Copyright © 1998.
All rights reserved. No part of this book may be reproduced or transmitted in any form or by any means, electronic or mechanical, including photocopying, recording or by any information storage retrieval system, without permission from the Publisher.
Russian language edition published by Williams Publishing House according to the Agreement with R&I Enterprises International, Copyright © 2001.
ISBN 5-8459-0149-9 (рус.)© Издательский дом “Вильямс”, 2001
ISBN 0-201-63488-0 (англ.)© Addison-Wesley Publishing Company, Inc.
Оглавление
Предисловие14
Вводная часть15
Часть I. Моделирование объектных баз данных19
Глава 1. Введение20
Глава 2. Базы данных, транзакции и исключительные ситуации 31
Глава 3. Моделирование объектных баз данных38
Глава 4. Домены46
Глава 5. Перманентные сущности61
Глава 6. Идентификация объектов76
Глава 7. Коллекции93
Глава 8. Связи112
Глава 9. Составные объекты130
Часть II. Язык объектных запросов139
Глава 10. Введение в язык объектных запросов140
Глава 11. Выражения и операторы языка OQL144
Глава 12. Предикаты и запросы коллекций165
Глава 13. Среда «обитания» OQL183
Часть III. Архитектура189
Глава 14. Архитектурные решения объектных баз данных190
Глава 15. Производительность207
Глава 16. Схемы баз данных223
Часть IV. Ретроспектива241
Глава 17. Сравнение технологий объектных и реляционных баз данных242
Глава 18. Резюме254
Часть V. Приложения261
Приложение А. Пример схемы262
Приложение Б. Классы ODMG C++265
Приложение В. Метаклассы ODMG345
Список литературы377
Предметнный указатель378
Содержание
Предисловие14
Вводная часть15
Для кого написана эта книга16
Роль фирм-производителей объектных баз данных16
Краткий обзор книги17
Благодарности18
Часть I. Моделирование объектных баз данных19
Глава 1. Введение20
Пример приложения21
ODMG: стандартизация объектных баз данных29
Глава 2. Базы данных, транзакции и исключительные ситуации31
Доступ к базе данных33
Транзакции33
Класс d_Transaction34
Двухфазное завершение35
Потоковые операции35
Исключения: обработка отказов36
Полное приложение, демонстрирующее транзакцию36
Глава 3. Моделирование объектных баз данных38
Модель данных вида сущность-связь39
Примитивы ER-моделирования39
Сходство в моделях баз данных40
Категории типов40
Сущности42
Домены42
Коллекции43
Составные сущности43
Объединение, абстракция и атомарность44
Глава 4. Домены46
Домены в других технологиях баз данных46
Категории доменов48
Примитивные литеральные типы48
Составные типы50
Абстрактные типы данных50
Типы доменов ODMG51
Класс d_String52
Класс d_Interval53
Класс d_Date54
Класс d_Time57
Класс d_Timestamp58
Ограничения по использованию некоторых типов58
Объединения59
Битовые поля60
Указатели и ссылки языка C++60
Заключительное слово о доменах60
Глава 5. Перманентные сущности61
Абстракция сущности61
Как сделать класс перманентным62
Базовый класс d_Object62
Экземпляры62
Свойства63
Атрибуты данных63
Операции64
Создание65
Создание транзитных экземпляров65
Создание перманентных экземпляров66
Кластеризация экземпляров66
Инициализация при создании66
Удаление67
Активизация и дезактивизация68
Функции d_activate и d_deactivate69
Проблемы реализации активизации71
Модификация экземпляров71
Управление параллелизмомi73
Другие операции\73
Присваивание73
Равенство74
Сравнение74
Сравнение с реляционными базами данных74
Глава 6. Идентификация объектов76
Объектный идентификатор76
Инициализация и присваивание78
Нулевые ссылки79
Операции разыменования80
Копирование ссылки81
Равенство82
Прозрачный доступ83
Представление и реализация84
Присвоение имен объектам87
Идентификация на основе значений атрибутов89
Ключи89
Индексы89
Резюме91
Глава 7. Коллекции93
Коллекции и итераторы ODMG94
Множества95
Мультимножества96
Списки '96
Массивы96
Словари96
Типы элементов коллекций ODMG97
Обшие операции коллекций97
Поддержка элементов коллекции97
Копирование и присваивание коллекций98
Равенство коллекций98
Итерация99
“Частная жизнь” разных типов коллекций101
Подмножества и супермножества101
Операции, определенные для множеств101
Операции, определенные для коллекций d_List<T> и d_Varray<T>102
Именованные коллекции и экстенты103
Именованные коллекции103
Экстенты104
Другие операции над коллекциями105
Доступ к адресуемым объектам105
Замкнутый доступ105
Величина кардинального числа106
Сравнение автономных коллекций с встроенными107
Модификации коллекций108
Оптимизации коллекций108
Индексы коллекций108
Курсоры109
Реализация коллекций в реляционных базах данных109
Представление схемы базы данных109
ИндексыНО
Представление приложенияНО
РезюмеНО
Глава 8. Связи112
Определение и назначение связей112
Стороны связи112
Степень связи113
Кардинальное число113
Однонаправленные и двунаправленные связи114
Рекурсивные связи115
Интерфейсы связей115
Объявления однонаправленных связей в ODMG116
Объекты связей ODMG117
Поддержка ссылочной целостности119
Сценарий использования связи типа один-к-одному119
Сценарий использования связи типа один-ко-многим122
Сценарий использования связи типа многие-ко-многим125
Обход связей и использование выражений пути128
Глава 9. Составные объекты130
Примеры составных объектов131
Подобъекты132
Ссылки на объекты, которые не являются подобъектами134
Составной базовый объект134
Физическая организация составных объектов134
Операции■137
Резюме138
Часть II. Язык объектных запросов139
Глава 10. Введение в язык объектных запросов140
Задачи OQL141
OQL — функциональныйязыкзапросов142
Глава 11. Выражения и операторы языка OQL144
Доступ к объектам145
Именованные объекты145
Доступ к атрибутам и обход связей146
Разыменование147
Ссылки на null-объекты147
Выполнение операций с объектами148
Приведение типа объектных выражений148
Построение объектов и структур149
Атомарные литеральные выражения150
Примитивные доменные типы150
Арифметические выражения150
Операторы сравнения151
Булевы операторы152
Строковые выражения\ 152
Коллекции' 154
Создание литералов коллекций154
Операции, выполняемые над коллекциями155
Операции, применяемые к множествам и мультимножествам 156
Операции, применяемые к индексированным коллекциям (спискам и массивам)158
Операции, применяемые к словарям158
Преобразования коллекций159
Равенство161
Функции‘163
Приоритет операторов164
Глава 12. Предикаты и запросы коллекций165
Переменные итераторов165
Предикаты165
Навешивание квантора общности166
Навешивание квантора существования166
Составные предикаты166
select ... from ... where...167
Директива from169
Директива where171
Директива select172
Директивы group by и having174
Директива order by178
Соединения178
Определениеименованных запросов179
Подзапросы180
Правила дляобластей видимости имен181
Имена переменных итераторов181
Имена групп разделения.182
Поиск имен182
Резюме182
\
I Глава 13. Среда «обитания» OQL183
Запросы коллекций в C++183
Запросы для экстентов (d_Extent<T>)185
Запросы к базам данных в среде C++185
Архитектурные решения механизма обработки запросов187
|13.4.1. Определение места обработки запросов187
Выполнение операций187
. Часть III. Архитектура189
Глава 14. Архитектурные решения объектных баз данных190
Компоненты190
Организация базы данных192
Логическая организация базы данных и доступ к объектам 192
Физическая организация хранения объектов193
Отображение логической организации на физическую194
Архитектура класса клиент/сервер195
Кэш базы данных и процесс сервера197
Кэш и процесс приложения клиента202
Согласованность кэшей205
Резюме206
Глава 15. Производительность207
Архитектура баз данных207
Эффективное использование кэшей208
Расходы надисковые передачи211
Передачи “клиент/сервер”212
Параллелизм213
Оценка производительности и тестирование214
Аттестации общего характера214
Аттестации специального назначения214
Категории доступа215
Факторы, влияющие на производительность216
Доступ, реализуемый приложениями217
Доступ посредством запросов220
Объектная модель приложения и варианты доступа221
Глава 16. Схемы баз данных223
Захват схемы224
Доступ к схеме226
Области действия имен226
Модули227
Операции228
Домены228
Ссылки228
Коллекции229
Свойства229
Атрибуты230
,16.2.9. Структуры230
|16.2.10. Классы230
Связи231
Наследование231
Перечисления233
Псевдонимы типов233
Модификации схем233
Модификации на уровне класса234
Изменение наследования235
Коллекции235
Операции235
Многоступенчатые изменения236
Миграция экземпляров236
Преобразования236
Миграция баз данных238
Немедленное преобразование238
Отложенное преобразование238
Индексы и кластеризация239
Часть IV. Ретроспектива241
Глава 17. Сравнение технологий объектных и реляционных баз данных242
Модели, системы типов и языки243
Реляционная модель и среда243
Приложение в реляционной среде244
Поддержка объектов244
Язык объектной реализации245
Сравнение с объектными базами данных в C++246
Нормализация246
Доступ к данным247
Выполнение249
Поддержка объектов в SQL3249
Абстрактные типы данных для доменов столбцов250
SQL PSM251
Именованные типы строк и ссылочные типы252
Оценка SQL3252
Глава 18. Резюме254
Категории типов254
Двойные интерфейсы255
Изменения парадигмы255
Стандартизация256
Объектные и реляционные базы данных257
Взгляд в будущее257
Java '258
Часть V. Приложения261
Приложение А.Пример схемы262
Приложение Б.Классы ODM.G C++265
Имя: символы препроцессора266
Имя: все классы267
Имя: d_Bag<T>268
Имя: d_Collection<T>272
Имя: d_Database276
Имя: d_Date278
Имя: d_Dictionary<K,V>284
Имя: d_Error286
Имя: d_Extent<T>290
Имя: d_Interval292
Имя: d_Iterator<T>295
Имя: d_List<T>298
Имя: dObject301
Имя: d_OQL_Query303
Имя: d_Ref<T>307
Имя: d_Ref_Any310
Имя: d_Rel_List<T, МТ>312
Имя: d_Rel_Ref<T, МТ>316
Имя: d_Rel_Set<T,MT>320
Имя: d_Set<T>325
Имя: d_String329
Имя: d_Time332
Имя; d_Timestamp336
Имя: d_Transaction340
Имя: d_Varray<T>342
Имя: STL344
Приложение В. Метаклассы ODMG345
Имя: итераторы346
Имя: d_Access_Kind348
Имя: d_AIias_Type349
Имя: d_Attribute350
Имя: d Class351
Имя: d_Collection_Type355
Имя: d_Constant356
Имя: d_Enumeration_Type357
Имя: d_Exception358
Имя: d_Inheritance359
Имя: d_Keyed_Collection_Type360
Имя: d_Meta_Object361
Имя: d_Module362
Имя: d_Operation364
Имя: d Parameter366
Имя: d_Primitive_Type367
Имя: d_Property368
Имя: d_Ref_Type369
Имя: d_Relationship370
Имя: d_Scope371
Имя: d_Structure_Type372
Имя: d_Type373
Список литературы377
Предметный указатель378
Моей жене Тине, моим детям Джениферу и Джереми и моим родителям Джону и Лори.
Предисловие
Я имел огромное удовольствие работать с Дэйвом Джорданом (Dave Jordan) в течение нескольких последних лет. Без его вклада в разработку стандарта ODMG многие аспекты, в том числе и реализация ODMG на языке C++, были бы намного слабее. В настоящей книге Дэйв еще раз продемонстрировал высокое мастерство и способность точно излагать свои мысли, не допуская двусмысленного толкования, что очень важно для технической литературы.
Из этой книги вы узнаете, как писать приложения для работы с объектными базами данных в C++. Здесь вы получите довольно глубокое представление о системах объектных баз данных и поймете, в чем различия между реляционной и объектной технологиями построения баз данных. Кроме того, вы найдете четкое описание языка запросов OQL и объектной модели ODMG, а также познакомитесь с эксплуатационными характеристиками и возможностями различных продуктов объектных баз данных. Эта книга будет полезна всем, кто использует или собирается использовать объектные базы данных, и особенно она пригодится тем, кто работает с несколькими продуктами ведения баз данных.
О ценности вклада Дэйва в создание стандарта ODMG может судить только тот, кто понимает, насколько важно убедить производителей программных продуктов в необходимости принятия предлагаемого стандарта, который они смогут реализовать в самые короткие сроки и причем самым эффективным способом. Однако потребности программистов иметь простой и непротиворечивый стандарт уделялось до сих пор недостаточное внимание. В этой книге Дэйв доказал, что программисту просто необходимо работать с интерфейсом ODMG. Деятельность Дэйва “обрекает” индустрию и технологию создания баз данных на прогресс. Я твердо убежден, что без помощи таких людей, как Дэйв, хорошая технология была бы попросту загублена. Демонстрация существования поддержки стандартного подхода к объектным базам данных уже принесла этой индустрии сотни миллионов долларов и дала программистам технологию, которая находит сотни новых сфер применения. В этой книге содержится прекрасный обзор и исчерпывающее описание технологии с точки зрения программиста.
Рик Кэтл (Rick Cattell)
Вводная часть
Кажется, мне было предопределено свыше с самого начала моей карьеры заняться технологией объектных баз данных. Мои академические наставники неумышленно привели меня к ней. Майк Уоррен (Mike Warren), профессор психологии, еше в студенческие годы заинтересовал меня семантическим моделированием и другими компьютерными науками. Завершив учебу в 1979 году, я получил работу в NCR Corporation, г. Дейтон, шт. Огайо, и добивался степени магистра в области компьютерных наук в университете Wright State. В корпорации NCR мне довелось заниматься сетевым моделированием баз данных, причем в группе, которая решала задачу переноса системы UNIX на NCR-компьютер. Вместе с моим руководителем из университета Wright State, Бобом Диксоном (Bob Dixon), я увлекся объектной технологией как раз в то время, когда на суд специалистов был представлен язык Smalltalk-80. Мы работали над несколькими объектно-ориентированными проектами одновременно.
В 1983 году, получив степень магистра наук, я начал работать в отделении AT&T Bell Laboratories компании UNIX System Development Laboratories, г. Муррей Хил, шт. Нью-Джерси. Мне очень повезло, что моим руководителем стал Том Карджил (Тот Cargill). Он разрабатывал отладчик под названием pi (process inspector — инспектор процессов) в новом языке программирования, изобретенном Бьярни Страуструпом (Bjarne Stroustrup). Зная язык С и желая использовать объектную технологию, в 1984 году я приступил к изучению C++.
В начале 1985 года я перевелся в филиал Bell Labs в г. Колумбус, шт. Огайо, где и живу до сих пор. Наш проект имел прикладную направленность и был предназначен для управления циклами проектирования упаковочных товаров и процессами, используемыми для их производства. Я отвечал за проектирование схемы базы данных, а также курировал использование C++/OOD в этом проекте. Меня заинтересовала новая технология, именуемая объектными базами данных. CAD/CAM была одной из первых областей ее применения. Эта технология нравилась мне, поскольку в ней объединялись мои интересы к объектам и базам данных, и она подходила к проекту, над которым я работал. Однако в то время существовало слишком мало подходящих коммерческих продуктов, и к тому же они опирались на патентованные языки программирования специального назначения, которые замыкались сугубо на функциях баз данных. Я считал, что эта технология не заслужит признания на рынке, если в ней не будет использован язык общего назначения.
На конференции OOPSLA-85 я познакомился с Тимом Эндрюсом (Tim Andrews), представлявшим компанию Ontologik, которая создала коммерческий продукт ведения объектных баз данных Vbase. Эта компания разработала собственный объектный язык программирования СОР, в котором С сочетался со специальными расширениями, предназначенными для работы с объектами и базами данных. Я убедил Тима и других специалистов компании Ontologik отказаться от СОР и использовать вместо него язык C++. Мы оговорили возможность сотрудничества компаний Bell Labs и Ontologik в целях разработки продукта поддержки объектных баз данных C++, но у руководства компании Bell Labs это предложение не вызвало интереса. Компания Ontologik самостоятельно взялась за разработку этого продукта, а я стал членом технической экспертной комиссии (Technical Advisory Board). В 1987 году компания выпустила первый коммерческий продукт поддержки объектных баз данных в среде C++. Я попытался убедить разработчиков моего проекта и других специалистов Bell Labs в использовании этой технологии, но мне это не удалось из-за отсутствия необходимых стандартов и более сильных ее сторонников на рынке программных продуктов.
В 1992 году я узнал, что Рик Кэтл (Rick Cattell) организовал консорциум фирм- производителей и пользователей объектных баз данных, именуемый группой управления объектными базами данных (Object Database Management Group — ODMG). Цель этой группы — определить стандарты для объектных баз данных, которые были бы обязательны для всех фирм-производителей. Наконец-то свершилось! Стандартный интерфейс, поддерживаемый всеми фирмами, обязательно приведет к благосклонному отношению на рынке. Все заинтересованные фирмы-производители выдвинули по несколько человек в качестве рецензентов стандарта. Джошуа Дахл (Joshua Duhl), ODMG-представитель компании Ontologik, выбрал рецензентом меня. В 1993 году Рик Кэтл предложил мне стать редактором ODMG C++.
Я пришел в полный восторг. Рик дал мне невероятный шанс! Группа ODMG только что выпустила спецификацию 1.0. Я присоединился к этой группе в качестве редактора C++ для версий 1.1, 1.2 и 2.0. Реализации этого стандарта сейчас есть у многих фирм-производителей.
В этой книге описаны объектные базы данных, использующие интерфейсы ODMG C++ и Object Query Language (OQL). Данная книга является введением в технологию объектных баз данных и может послужить справочником для всех, кто использует какую-либо из реализаций стандарта ODMG.
Для кдго написана эта книга
Эта книга предназначена для разработчиков программных продуктов, которые знакомы с объектным проектированием и языком C++ и хотят узнать об объектных базах данных. Менеджерам рекомендуется прочитать книгу Мэри Лумис (Магу Loomis) Object Databases: The Essentials, а также другие книги, содержащие более подробное описание реализации объектных баз данных. Например, в качестве прекрасного справочника может служить книга Рика Кэтла Object Database Management.
Роль фирм-производителей объектных баз данных
В этой книге не предполагалось рассматривать никаких конкретных фирм- производителей или реализаций. Со временем реализации меняются, поскольку архитектурные решения адаптируются к усовершенствованиям в компьютерных средах и к требованиям со стороны пользователей. Многие книги, в которых рассматривались реализации объектных баз данных, уже устарели, потому что технология ушла далеко вперед.
Я решил не сравнивать работу фирм-производителей напрямую. Мне, как редактору ODMG C++, необходимо поддерживать хорошие отношения со всеми производителями. Если бы я пошел по пути сравнения, то не исключено, что многие из них обиделись бы на меня. Людям свойственно сильнее реагировать на негативные отзывы, чем на положительные. Каждое реализованное решение имеет как положительные, так и отрицательные стороны. У разработчиков приложений предпочтения основываются на системных требованиях, которые заставляют делать выбор в пользу лучшей (с точки зрения требований данного приложения) архитектуры. Читателям, которые хотят разобраться в средствах, применяемых разными фирмами- производителями, следует приобрести книгу Дуга Берри (Doug Barry) The Object Databases Handbook: How to Select, Implement, and Use Object-Oriented Databases.
Краткий обзор книги
Эта книга разделена на пять частей. Часть 1 посвящена моделированию объектных баз данных. В первой главе приведен пример разработки полного приложения с использованием объектных баз данных в среде C++. Во второй главе показано, как открыть базу данных, начать и завершить транзакции. Возможности объектного моделирования, заложенные в среде поддержки объектных баз данных, раскрыты в главах 3— 9. В качестве интерфейса служит стандарт ODMG 2.0. Здесь не рассматривается ни один из интерфейсов конкретных фирм-производителей, хотя и обсуждаются некоторые аспекты различных реализаций.
В части И представлен язык объектных запросов (Object Query Language — OQL), который принят группой ODMG в качестве стандартного языка запросов для объектных баз данных. Если вы еще незнакомы с OQL, эту часть следует читать как справочное руководство по языку. Исторически сложилось так, что поддержка языка запросов — одно из слабых мест объектных баз данных, особенно в сравнении с реляционными базами данных. Некоторые производители объектных баз данных реализовали собственные варианты SQL-доступа, причем каждый из них преобразует объектную модель в SQL по-разному, что приводит к потере переносимости приложений (несмотря на использование одного и того же языка запросов). OQL — это мощный язык запросов для объектов, и его реализация всеми производителями заметно улучшит “репутацию”, т.е. приемлемость, объектных баз данных. Те фирмы, которые еще не реализовали OQL, по-прежнему поддерживают язык SQL. Если разработчики заинтересованы в стандартизации языков запросов для объектных баз данных, они должны настаивать, чтобы производители поддерживали реализацию OQL.
В части Ill рассмотрены различные архитектуры, используемые объектными базами данных. Вместо того чтобы переписывать свои системы с нуля, большинство компаний устанавливают интерфейс ODMG на уже существующие архитектуры. Между архитектурами фирм-производителей существует много различий, что не может не
сказаться на степени пригодности того или иного продукта для конкретного приложения. При выборе продукта очень важно знать об этих различиях. Здесь также за
тронуты вопросы, связанные с производительностью, поскольку эта характеристика объектной базы данных часто является производной от используемой архитектуры. Завершается эта часть описанием схем баз данных.
В части IV рассматриваются реляционные и объектно-реляционные базы данных в
сравнении с объектными базах^зПаМ'бйё.'ХЗЗбЖ-ФГО^х^дайЭДовн
поддержке в эскизной версии Ai жения относительно будущего |
граммирования Java).
№^ШЖ&5ИЙгНА¥МШ» некоторые сбобра-
той из этих технологий (касательно языкИ про- ентралъ:+с --v v:;’
Часть V включает три приложения. Приложение А содержит объявления классов, используемых в книге. Приложения Б и В представляют собой полный справочник по интерфейсу ODMG 2.0 C++. Поскольку реализации, доступные на рынке, все еще основываются на интерфейсе ODMG-93 1.2, я отметил различия между этими версиями. Приложение В посвящено метаклассам, представленным в интерфейсе ODMG 2.0. Эти классы используются приложениями для доступа к описанию схемы базы данных. Обзор этих классов приведен в главе 16.
благодарности
Я хотел бы поблагодарить всех, кто великодушно предоставил программы, которые я использовал при написании примеров. Фрэнцойс Бенсилхон (Francois Bancilhon) из компании 02 Technology и Дирк Бартелз (Dirk Bartels) из компании Poet Software пожертвовали своими реализациями стандарта ODMG-93 версии 1.2. Ричард Патерсон (Richard Patterson) из компании Microsoft снабдил меня компилятором Visual C++ 4.0, а Крис Тарр (Chris Tarr) из ObjectSpase любезно предоставил реализацию стандартной библиотеки шаблонов (STL).
Я глубоко признателен всем, кто нашел время и силы на рецензирование этой книги. В их число входят члены ODMG: Софи Геймерман (Sophie Gamerman) из компании 02 Technology, Ричард Дженсен (Richard Jensen) из корпорации Persistence Software Inc., Рэймонд Лэй (Raymond Lai) из компании Versant Object Technology, Олаф Шедоу (Olaf Schadow) из Poet Software и Кен Синклер (Ken Sinclair) из фирмы Object Design, Inc., a также Донна Отри (Donna Autrey), Картер Гласс (Carter Glass), Говард Ли Хакнес (Howard Lee Harkness), Тина Джордан (Tina Jordan), Брайен Керниган (Brian Kernighan), Эд Шибел (Ed Schiebel) и Кэтси Старк (Kathy Stark). Я также высоко ценю помощь, оказанную Мартином Фоулером (Martin Fowler) при создании схем UML. Особая благодарность моему другу Денису Лейнбаху (Dennis Leinbaugh), который не только просмотрел эту работу несколько раз и предоставил расширенные редакторские комментарии, но и просто таки был моим коллегой по генерации идей во время создания книги. Кроме того, я хотел бы поблагодарить дружную команду издательства Addison-Wesley, включая Кэти Даффи (Katie Duffy), Марину Лэнг (Marina Lang), Памелу Йи (Pamela Yee) и Джеки Янг (Jacqui Young). Отдельно хочется поблагодарить редактора Бетси Гар- дингер (Betsy Hardinger), поскольку эта книга и, надеюсь, мои будущие книги сильно выиграют благодаря сведениям, почерпнутым мною из ее редакторских комментариев. Наконец, я не могу не высказать искреннюю признательность Майку Хендриксону (Mike Hendrickson) и Джону Уэйту (John Wait) из издательства Addison-Wesley за предоставленную мне возможность издать эту книгу.
Часть I
Моделированиеобъектных баз данных
Глава 1
Введение
При работе с объектной базой данных в среде C++ приложение и сама база данных используют единую объектную модель. Объявления С++-классов приложения служат в качестве определения схемы базы данных, тем самым позволяя разработчикам C++ работать в знакомой языковой среде, которая одновременно является средой моделирования. Интерфейс для работы с базой данных бесшовно интегрирован с приложением. Разработчикам не нужно определять модель приложения как в среде C++, так и в отдельной среде языка ведения и моделирования баз данных. Кроме того, нет необходимости в определении соответствия моделей приложения и базы данных. Приложение и база данных разделяют единую расширенную систему типов. В этой обшей модели и состоит главная особенность объектной базы данных и первопричина ее ценности.
В этой главе мы вместе пройдем через процесс создания приложения, используя объектную базу данных. Здесь вы узнаете:
как определить схему базы данных;
как создать новый экземпляр объекта в базе данных;
как связать имя с экземпляром, а затем получить доступ к этому экземпляру по имени;
как установить связи между экземплярами;
как получить доступ к связанным экземплярам путем навигации;
как модифицировать экземпляр;
как удалить экземпляр.
На примере приведенных приложений вы увидите, что все перечисленные выше операции выполняются с помощью стандартного синтаксиса языка C++. Все приложения, работающие с базами данных, не могут обойтись без этих основных операций.
В этой книге используется интерфейс стандарта группы управления объектными базами данных (Object Database Management Group — ODMG). ODMG — это консорциум фирм-производителей и пользователей объектных баз данных, которые определяют стандартные интерфейсы приложений для объектных баз данных. Каждый член консорциума берет на себя обязательство поддерживать этот интерфейс в своих продуктах. Я работал в качестве редактора ODMG C++ с 1993 года. Некоторые фирмы-
производители (члены этого консорциума) уже выпустили реализации своих продуктов с учетом новых стандартов. Полное описание классов ODMG приведено в приложениях Б и В, поэтому при необходимости вы можете обращаться к ним за справочной информацией.
Пример прилажения
Данный пример приложения используется для управления списками подарков, предназначенных для вручения людям. Объектная модель состоит из двух классов: GiftList и Gift. Класс GiftList моделирует множество людей и приготовленные для них подарки. Возможно существование нескольких списков подарков. Класс GiftList поддерживает односвязный список объектов класса Gift. Эти классы и отношения между ними показаны на рис. 1.1.
Рис. 1.1. Связи между классами GiftList и GiftРис. 1.1. Связи между классами GiftList и Gift
Рис. 1.1. Связи между классами GiftList и Gift
Рис. 1.1. Связи между классами GiftList и Gift
В этой книге используется U ML-запись, т.е. запись, принятая в унифицированном языке моделирования (Unified Modeling Language — UML). Между классами GiftList и Gift существует связь типа один-ко-многим: каждый экземпляр класса Gift связан с одним экземпляром класса GiftList, а каждый экземпляр класса GiftList может быть связан с несколькими экземплярами класса Gift.
Предположим, что эти классы объявлены в заголовочном файле giftlist .hxx:
public:
Gift(const char const char Gift() ;
*fn, const
*gift, unsi
char gned
‘In, long c)
friend ostream s friend istream s private:
char char char unsigned long Gift *
1;
operator<<(ostre operator>>(istre
first_name[16]; last_name[16]; gift_descr[30];
cost;
next;
am s, const am S, Gift
Gift
S) ;
: S) ;
class Gift {
friend class GiftList;
friend ostream & operator<<(ostream &, const GiftList &);
class GiftList ( public:
GiftList(const char *n, unsigned long b) ;
friend ostream s voidfriend ostream s void-GiftList() ;
friend ostream s void
friend ostream s void
operator«(ostream s, const GiftList S) ; addGifts (istream S) ;
private:
charname[20];
unsigned longbudget;
Gift *gifts;
Класс Gift содержит имя человека, получающего подарок, описание подарка и его стоимость. Поддерживается односвязный список экземпляров класса Gift, причем “голова” этого списка находится в экземпляре класса GiftList. Класс Gift содержит указатель next, который используется для ссылки на следующий элемент списка. В классе GiftList определен указатель gifts, который содержит адрес первого элемента списка или значение 0, если в списке элементы отсутствуют. Экземпляр класса GiftList также имеет символьный массив name и переменную целочисленного типа budget для хранения бюджета, который не должен быть превышен. Как видите, это обычные классы, которые можно объявить в любом приложении C++. Согласно приведенным выше объявления, любой экземпляр каждого из этих классов является транзитным (нерезидентным). Это означает, что он существует только во время выполнения процесса приложения, в котором он создан, и не хранится в базе данных.
Файл определения классов содержит конструкторы для этих двух классов.
#include <giftlist.hxx>
GiftList::GiftList(const char *nm, unsigned, long b) : budget(b) { strncpy(name, nm, 19); name[19] = 0;
}
Gift: ".Gift (const char *fn, const char *ln, const char *gift, unsigned long c) : cost(c) { strncpy(first_name, fn, 15); first_name[151 = 0; strncpy(last_name, In, 15); last_name[15] = 0; scrncpy(gift_descr, gift, 29); gift_descr[29] = 0;
}
Gift: :Gift() : cost (0) { first_name[0] = 0; last_name[0] = 0; gift_descr [0];
}
Конструкторы инициализируют данные-члены класса.
В примерах этой книги используется библиотека C++ iostreams. Большинство С++-программистов знакомы с этой библиотекой, которая должна быть доступна во всех средах C++. Операторы operator<< и operator>> перегружены для классов ostream и istream, чтобы обеспечить работу с потоками вывода и ввода соответственно. Они используются в примерах для вывода значений экземпляров и считывания данных инициализации.
Предположим, мы решили хранить экземпляры этих классов в объектной базе данных. Тогда в эти классы необходимо внести ряд изменений, чтобы сделать их способными к длительному хранению, т.е. “привить” им свойство живучести, или перманентности (persistence-capable). Под перманентностью подразумевается, что объект будет храниться в базе данных даже после выполнения процесса приложения. Экземпляр класса, обладающего свойством перманентности, может храниться в базе данных.
Прежде всего, приложение должно создать базу данных. Для этого предусмотрена либо специальная команда, либо графический интерфейс. Стандарт ODMG не предписывает, как именно создается база данных; у разных фирм-производителей эта процедура может быть реализована по-разному. Согласно стандарту ODMG, каждая база данных имеет имя. Итак, допустим, с помощью инструментария, предложенного производителем, мы создали базу данных с именем Gifts (Подарки).
Затем определяем схему базы данных. В объектной базе данных C++ схема выводится из классов C++, объявленных в приложении. Утилита, именуемая средством захвата схемы (schema capture tool), считывает заголовочные файлы приложения, которые используются для генерации схемы. Средство захвата схемы может также генерировать дополнительный код, который должен быть скомпонован с этим приложением. На рис. 1.2 показана процедура компиляции приложения, работающего с объектной базой данных.
Рис. 1.2. Процедура компиляции приложения
Перед запуском средства захвата схемы необходимо внести в классы C++ несколько изменений. В ODMG класс приобретает свойство перманентности путем выведения его из базового класса d_Object. (Имена всех классов ODMG C++ начинаются с префикса d_.) Поэтому классы Gift и GiftList должны быть выведены из класса d_Object.
Некоторый указатель может содержать адрес объекта в памяти, но значение этого адреса не имеет никакого смысла для процесса другого приложения. Следовательно, нельзя просто сохранить значение указателя в базе данных и при этом надеяться, что оно будет использовано другим приложением. ODMG определяет класс так называемых интеллектуальных указателей (smart-pointer class) d_Ref<T> для представления ссылки на экземпляр класса т. Экземпляры этого класса могут храниться в базе данных и использоваться для ссылки на объекты базы данных. Класс т должен быть выведен из класса d_Object. Класс d_Ref<T> имеет интерфейс, подобный указателю. Чтобы сохранить экземпляры наших классов в базе данных, необходимо заменить указатели переменными типа d_Ref<T>.
Этих изменений (они выделены в приведенном ниже фрагменте программы полужирным шрифтом) достаточно, чтобы сделать классы Gift и GiftList перманентными, т.е. способными к продолжительному хранению. Предположим, что фирма- производитель предоставила заголовочный файл с именем odmg.h, который содержит объявления классов ODMG. Этот заголовочный файл (заголовок) нужно включить в программу до использования классов ODMG. Во время разработки стандарта ODMG пространства имен еще не получили широкого распространения в компиляторах. После того как большинство компиляторов станут поддерживать пространства имен, типы реализаций баз данных будут относиться к пространству имен, именуемому odmg. Поэтому в настоящей книге пространства имен не используются.
#include Coding.h>
class Gift : public d_Object {
friend class GiftList;
friend ostream &operator<<(ostream s, const GiftList s);
public:
Gift(const char *fn, const char *ln, const char *gift, unsigned long c);
Gift () ;
friendostream &operator« (ostream s, const Gift &) ;
friendistream &operator>>(istream &, Gift &);
private:
charfirst_name[16];
charlast_name[16];
chargift_descr[30];
unsigned long cost;
d_Kef<Gift>next;
};
class GiftList : public d_Object {
public:
GiftList(const char *n, unsigned long b) ;
-GiftList();
friend ostream &operator« (ostream &, const GiftList S);
voidaddGifts(istream &) ;.
private:
charname[20];
unsigned longbudget;
d_Kef<Gift>gifts;
};
Оба класса теперь выведены из базового класса d_Object, и для ссылки на экземпляр вместо указателя они используют переменные типа d_Ref<T>. Эти определения классов вполне готовы для обработки средством захвата схемы. Член gifts класса GiftList и член next класса Gift не были явно инициализированы в исходных конструкторах этих классов. Если бы они имели тип указателей, то после инициализации
включающего объекта их значения могли быть какими угодно (т.е. мусором). Но класс d_Ref<T> имеет нулевой конструктор (null-конструктор), который инициализирует свой экземпляр нулевым значением (null-значением). Конструкторы классов Gift и GiftList автоматически вызовут нулевой конструктор d_Ref<T>, поскольку члены этих классов (ссылки) не инициализируются явным образом.
После завершения работы средства захвата и определения схемы приложение может вставлять объекты в базу данных (в нашем случае — в базу данных с именем Gifts). Для выполнения операций над объектами в базе данных мы используем два класса ODMG: d_Database и d_Transaction. Взаимодействие приложения с базой данных должно осуществляться посредством транзакции, которая представлена экземпляром класса d_Transaction. Чтобы открыть базу данных, используем класс d_Database. Экземпляру класса приложения может быть присвоено имя, которое является уникальным внутри базы данных. Для доступа к объекту этим именем может легко воспользоваться другое приложение. В классе d_Database определены операции, позволяющие присваивать экземплярам имена и осуществлять к ним доступ по имени.
Следующее приложение создает перманентный экземпляр класса GiftList и присваивает ему имя.
#include <odmg.h>
d_Database db;
const char * const db_name = "Gifts";
int mainfint argc, char *argv[]) (
db.open(db_name);//открывает именованную базу данных
d_Transaction tx;
tx.begin();// начинает транзакцию базы данных
char *name = argvfl];
unsigned long budget = aroi(argv[2]);
GiftList *giftlist = new(&db, "GiftList")
GiftList(name, budget);
db.set_object_name(giftlist, name);
tx.commit();// завершает транзакцию
db.closet);// закрывает базу данных
return 0;
}
В этой программе предполагается, что приложение имеет надлежащее количество и тип параметров. Мы объявляем экземпляр класса d_Database с именем db глобально, чтобы к нему могли получить доступ все функции в приложении. Обратите внимание на выделенный в этой программе вызов оператора new. Этот оператор перегружен в базовом классе d_object, чтобы принимать указатель на экземпляр класса d_Database. С помощью вызова оператора new создается перманентный экземпляр класса GiftList в базе данных, связанной с переменной db. А уже в следующей после этого вызова строке переменная db используется для связи имени (содержащегося в символьном массиве name) с экземпляром класса GiftList.
Это вполне законченное приложение. Двух строк (они выделены полужирным шрифтом) программного кода достаточно, чтобы создать экземпляр и присвоить ему имя. При этом приложению совсем не нужно использовать такой встроенный язык баз данных, как SQL. Более того, не требуется устанавливать никакого соответствия между представлениями объекта в приложении и базе данных. Но приложение должно быть скомпоновано с библиотеками фирмы-производителя баз данных и с некоторыми объектными файлами, сгенерированными во время захвата схемы.
Теперь давайте напишем приложение для доступа по имени к экземпляру класса GiftList и выведем его в стандартный поток вывода.
#include <odmg.h>
d_Database db;
const char * const db_name = "Gifts";
int
main(int argc, char *argv[]) {
db.open(db_name); //открывает именованную базу данных d_Transaction tx;
tx.begint);// начинает транзакцию базы данных
char *name = argv[l];
d_Ref<GiftList> giftlist = db.lookup_object(name);
if ( giftlist.is_null() ){
cerr << "No gift list with the name"; // нет списка подарков // с таким именем
cerr << name << endl; return -1;
}
cout << *giftlist; // эта строка заменяется
// в последующих транзакциях
tx.commit();// завершает транзакцию
db.closed;// закрывает базу данных
return 0;
}
В последующих примерах этой главы используется эта же функция main, но в них будет заменена строка вывода значения экземпляра класса GiftList. Функция-член класса d_Database, lookup_object, возвращает ссылку на именованный экземпляр. Если заданное имя не соответствует ни одному объекту в базе данных, возвращается нулевая ссылка. Объявляется переменная giftlist типа d_Ref<GiftList>, которая инициализируется возвращаемым значением функции lookup_object. Функция- член is_null является булевой. В стандарте ODMG определен тип d_Boolean со значениями d_False и d_True (нуль и ненулевое значение соответственно). Функция is_null возвращает истинное значение, если переменная giftlist является нулевой ссылкой, или ложное, если она действительно ссылается на некоторый экземпляр класса GiftList.
Если переменная giftlist ссылается на существующий экземпляр класса GiftList, то следующая строка записывает этот объект в стандартный поток вывода. Оператор operator« требует наличия ссылки на объект GiftList. Для этого необходимо применить к переменной giftlist оператор разыменования (*), определенный в классе d_Ref<GiftList>. Следует отметить, что если бы переменная giftlist являлась указателем на объект, а не имела тип d_Ref<GiftList>, все равно потребовался бы точно такой же синтаксис. При вызове функции-члена lookup_object возвращается лишь ссылка на экземпляр, а не сам экземпляр. Применение оператора разыменования приводит к тому, что к объекту в базе данных автоматически осуществляется доступ и он реализуется в приложении. При этом от приложения не требуется никакого дополнительного программного кода для преобразования объекта из представления базы данных в C++-представление.
Класс GiftList содержит функцию-член addGifts, которая считывает из потока ввода по одному подарку (в каждой строке), пока не будет достигнут конец файла (т.е. пока не будет прочитано значение EOF). Каждая строка содержит информацию, необходимую для инициализации экземпляра класса Gift.
void
GiftList:: addGifts (istream sis) {
Gift *gift;
mark_modif led () ;
while( is.peek() != EOF ){
gift = new(&db, "Gift") Gift(); // создаем
// экземпляр класса Gift
is >> *gift;// инициализируем значением из потока
gift->next = gifts; // помещаем в "голову" списка gifts = gift;
}
Эта функция модифицирует объект класса GiftList. Прежде чем можно будет модифицировать объект базы данных, необходимо вызвать функцию markjnodified, которая определена в базовом классе d_Object. Это единственный фрагмент программного кода, который нужно добавить в приложение. Как и прежде, оператору new передается адрес переменной db для создания в базе данных экземпляров класса Gift. Для вновь созданных экземпляров не нужно вызывать функцию mark_modified. Посредством выполнения операций присваивания соответствующих значений члену gifts класса GiftList и члену next класса Gift в базе данных устанавливается связь между экземплярами. Функцию-член addGifts можно было бы усовершенствовать, чтобы она “отказывалась” добавлять очередной подарок, если он “грозится” превысить бюджет, установленный в списке подарков.
Мы не будет повторять здесь функцию main. Примем предыдущее ее определение, за исключением строки
cout « *giftlist;,
которая заменяется следующей.
giftlist->addGifts(cin);
Переменная giftlist вызывает функцию-член addGifts. Функция operator-> определена в классе d_Ref<GiftList> для возвращения указателя на экземпляр класса GiftList. Подобно функции operator*, эта функция сначала реализует экземпляр класса GiftList в памяти приложения, а затем возвращает на него указатель.
Обратите внимание, что приложение не присваивает имя каждому отдельному экземпляру класса Gift. (Чаще всего этой чести удостаивается только небольшой процент от числа всех объектов.) Если к такому первичному объекту, как экземпляр класса GiftList, разрешается доступ по имени, то к связанным с ним объектам можно получить доступ, переходя по объектным ссылкам.
Чтобы удостовериться, что экземпляры класса Gift действительно были добавлены в класс GiftList, мы используем оператор вывода в поток. Функция operator<< перегружается для вывода экземпляра класса GiftList.
ostream s
operator«(ostream sos, const GiftList sgl) {
d_Ref<Gift> gift;
os « gl.name « " " « gl.budget « endl;
for( gift = gl.gifts; !gift.is_null(); gift = gift->next ) os « *gift;
return os;
},
В цикле for выполняется опрос всех экземпляров класса Gift, которые связаны с конкретным экземпляром класса GiftList. Навигация по экземплярам с помощью
объектной ссылки типа d_Ref<T> является основным средством доступа к связанным объектам в объектной базе данных.
Необходимо также написать программный код для удаления из базы данных экземпляра класса GiftList и связанных с ним экземпляров класса Gift. Эту задачу решает деструктор.
GiftList::-GiftList() {
d_Ref<Gift> gift, giftn;
for( gift = gifts; !gift.is_null(); gift = giftn ){ giftn = gift->next;
delete gift.ptr();
} }
В языке C++ для выполнения оператора delete требуется указатель на экземпляр. Класс d_Ref<T> содержит функцию-член с именем ptr, которая возвращает указатель на заданный экземпляр. Функция operator-> используется для получения доступа к члену класса. Если же вас может устроить только указатель на экземпляр, вызовите функцию ptr.
Для транзакции удаления используется та же функция main. Строку
cout « *giftlist; // заменяется одна эта строка
можно заменить следующей.
delete giftlist.ptr();
Функция ptr вызывается для экземпляра класса d_Ref<GiftList>, чтобы получить указатель на экземпляр класса GiftList. При выполнении оператора delete вызывается деструктор GiftList, который удаляет каждый из связанных экземпляров класса Gift. Затем из памяти приложения и базы данных удаляется экземпляр класса GiftList.
Мы показали, как использовать объектные ссылки для реализации экземпляров в приложении. В большом приложении может существовать много ссылок на один и тот же экземпляр. Объект реализуется в приложении, когда происходит разыменование первой такой ссылки. Объектной базе данных известно, какие объекты были реализованы в приложении, причем в приложении возможна только одна реализация объекта. Все последующие запросы от других ссылок на доступ к тому же экземпляру возвращают указатель на уже существующую реализацию объекта, что не требует никаких дополнительных расходов, связанных с новым доступом к базе данных. Тем самым значительно снижается стоимость разработки приложений и экономятся системные ресурсы во время выполнения.
Для объектных баз данных характерна поддержка прозрачности. Поскольку база данных и приложение совместно используют одну систему типов, преобразование объектов из одного представления в другое может совершаться совершенно незаметно. Поэтому объектная база данных гораздо меньше “видна” для приложения, чем при использовании других технологий ведения баз данных. Сложность приложения и стоимость разработки снижаются, когда разработчики управляют только одной моделью данных, вместо того чтобы управлять несколькими и к тому же выполнять преобразования между ними.
Объектная модель C++ служит не только в качестве схемы, но и как интерфейс базы данных. Поскольку приложения циклически опрашивают коллекции и разыменовывают объектные ссылки, обработка базы данных выполняется “под колпаком”. Казалось бы, приложения работают с объектами-резидентами в памяти, однако на самом деле операции обхода связей охватывают и “территорию” базы данных, если объекта еще нет в приложении. Но эта “шпионская” деятельность скрыта от приложения.
Программы работы с базами данных поддерживают кэш объектов для приложения, что способствует прозрачности базы данных. Кэш объектов представляет собой область памяти, принадлежащую приложению и содержащую объекты базы данных, доступ к которым совершается самим приложением. Когда приложение собирается изменить объект, оно вызывает функцию mark_modifled, определенную в базовом классе d_Object.
На базу данных не нужно распространять обновления, происходящие в приложении. Когда приложение завершает транзакцию, программа обслуживания в базу данных автоматически записывает необходимые объекты. Такой подход существенно снижает стоимость разработки приложения. Библиотеки, которые модифицируют объекты, можно разрабатывать и использовать без специального механизма согласования с приложениями, использующими эти библиотеки.
ODMG: стандартизация объектных баз данных
До недавнего времени для технологии объектных баз данных не существовало ни одного промышленного стандарта. Каждая фирма-производитель имела собственный интерфейс. Архитектуры, положенные в основу реализаций объектных баз данных, также существенно отличались одна от другой. Без стандартного интерфейса судьба любого приложения, работающего с базой данных, зависит от признания на рынке производителя реализации базы данных, выбранного разработчиками этого приложения. Фирмы-производители продуктов объектных баз данных относительно невелики, и зависящие от них компании могут сильно пострадать, если у этих фирм возникнут финансовые трудности. Вот почему многие организации, занимающиеся разработкой, не горят желанием использовать эту технологию. Стандартизация интерфейсов приложений значительно уменьшит зависимость от производителя базы данных, повысит уровень переносимости и уменьшит риск принятия этой технологии.
Стандарт ODMG состоит из нескольких взаимосвязанных компонентов. Реализация ODMG требуется для поддержки только некоторых из них. Как минимум, производитель должен поддерживать один из языков: C++, Smalltalk или Java.
ODMG определяет объектную модель, которая поддерживает множество примитивов объектного моделирования, используемых приложением. Другие компоненты предназначены для поддержки конструкций, определенных в этой объектной модели. Модель содержит описание свойств, которые могут ассоциироваться с объектом (к ним относятся атрибуты, связи и операции).
Эта модель поддерживает единичное и множественное наследования, конкретные и абстрактные классы. (Конкретные классы могут иметь экземпляры, а абстрактные — нет.) Можно определить экстенты для содержания всех экземпляров класса. Экземпляры класса, обладающего свойством перманентности, могут быть либо транзитными, либо перманентными.
В этой книге не описывается конкретная объектная модель ODMG, но здесь уделено внимание всем ее компонентам моделирования. Объектная модель поддерживается ODMG-интерфейсами, использующими языки C++ и OQL (эти интерфейсы рассмотрены в настоящей книге достаточно подробно). Если вас интересует полное описание объектной модели ODMG, можно достать спецификацию ODMG из Web. Web-узел ODMG имеет адрес www.odmg.org. Здесь вы можете больше узнать об организации и стандарте ODMG.
Язык определения объектов (Object Definition Language — ODL) служит для определения схем объектных баз данных, которые не зависят от языка программирования, используемого приложением. ODL представляет собой расширенный вариант языка определения интерфейсов CORBA (CORBA Interface Definition Language — IDL), принятого
группой Object Management Group, а также поддерживает возможности моделирования, определенные в объектной модели ODMG. Расширения ODMG поддерживают такие конструкции, как связи, без которых нельзя обойтись в среде баз данных.
Реализации ODMG могут использовать язык ODL как основу для определения схемы. ODL-элементы служат в качестве исходного “сырья” для заполнения словаря базы данных и генерации объявлений классов C++, используемых приложением. ODMG не определяет, каким образом ODL преобразуется в объявления классов C++. (С++-представления языка ODL будут различными для реализаций разных фирм- производителей.) Следовательно, использование ODL для определения схемы не будет переносимым по реализациям ODMG. Однако лишь немногие производители планируют для этого использовать средства ODL вместо объявлений классов C++, определяемых программистами приложения. Исторически сложилось так, что разработчики предпочитали вариант выведения схемы из объявлений их классов. Поэтому в настоящей книге язык ODL не рассматривается.
Если бы были определены преобразования из языка ODL в каждый язык программирования приложения, то ODL можно было использовать в качестве единственного средства определения схемы, которое могло генерировать соответствующие определения классов в каждом языке программирования. Однако язык ODL испытывает недостаток в языковых конструкциях, которыми разработчики предпочитают пользоваться, поэтому для многих приложений будет явно недостаточно объявлений, сгенерированных средствами ODL. Максимум, что может обеспечить ODL, — “наименьшее обшее кратное" от языковых средств среди поддерживаемых языков программирования. '
Язык объектных запросов (Object Query Language — OQL) определен стандартом ODMG. OQL предоставляет прекрасный декларативный доступ к объектам в объектной базе данных. Он напрямую поддерживает объектную парадигму: операции, связи, наследование, полиморфизм и т.д. Возможности языка OQL описаны в главах 10—13.
Версия 2.0 стандарта ODMG, выпущенная в 1997 году, указывает на возможность использования таких языков программирования, как C++, Java и Smalltalk. В этой версии исправлены некоторые ошибки, обнаруженные в версии 1.2, но она состоит, главным образом, из новых коллекций, экстентов и метаклассов. В этой книге акцент ставится на объектных базах данных C++ и самой версии 2.0 стандарта ODMG. Полное определение интерфейса ODMG 2.0 C++ приведено в приложениях. Реализации интерфейсов ODMG 1.2 C++ можно уже приобрести, а реализация версии 2.0 — дело ближайшего будущего. Поэтому все примеры программ в этой книге ориентированы на версию 1.2.
Мы стоим на пороге новой эры использования объектных баз данных. В 1996 году впервые появились коммерческие продукты, основанные на стандарте ODMG. До появления этого стандарта каждая фирма- производитель предлагала только свой интерфейс. Стандартизация, конечно же, расширит рынок. Производители реляционных баз данных долго наслаждались признанием на рынке, в основном, благодаря стандарту SQL, но эта ситуация может скоро измениться. Производители реляционных баз данных разрабатывают объектно-реляционные продукты, которые существенно отличаются от объектных баз данных. Даже несмотря на то что комитет ANSI SQL работает над определением SQL3 (стандарта для использования объектов в реляционных базах данных), каждый производитель выпускает собственный объектный интерфейс. В результате на рынке объектно-реляционных продуктов будет сильно ощущаться недостаток стандартизации. Документ, который определяет стандарт, не будет иметь смысла, если поставщики не станут поддерживать интерфейсы, удовлетворяющие этому стандарту. Стандарт SQL3 и другие аспекты включения объектов в реляционные базы данных рассматриваются в главе 17.
Глава 2
Базы данных, транзакции и исключительные ситуации
В приложениях часто возникает необходимость сохранять объекты в базе данных, чтобы другие приложения могли получить к ним доступ. База данных обеспечивает как долговременное хранение, так и координацию доступа со стороны нескольких приложений. Объект, сохраняемый в базе данных, называют постоянным, или перманентным (persistent). Его время существования выходит за пределы одного процесса приложения. Объекты, существующие только во время одного процесса, называются транзитными (transient).
Процесс серверабазы данныхПроцесс серверабазы данныхПроцесс приложения, получающего доступ к базе данных, обычно взаимодействует с ней как клиент с процессом сервера базы данных (рис. 2.1). Процессы нескольких приложений могут параллельно взаимодействовать с одной базой данных. Сервер базы данных координирует доступ между клиентами. Он поддерживает кэш базы данных, содержащий считанные с диска данные, к которым получают доступ приложения. Объект базы данных в процессе приложения также поддерживает кэш клиента. Кэш клиента уникален для объектных баз данных; другие технологии баз данных (например, реляционные базы данных) не обеспечивают управления кэшем клиента. Некоторые особенности и преимущества объектной базы данных являются следствием наличия именно кэша клиента.
Процесс серверабазы данных
Процесс серверабазы данных
Процесс приложенияклиента
Кэш клиентаКэш клиентаКэшбазы данных
Кэш клиента
Кэш клиента
Рис. 2.1. Архитектура среды базы данных типа клиент/сервер
Приложению обычно нужно иметь доступ к нескольким объектам базы данных. Эти объекты могут находиться либо в одной базе данных, либо в нескольких. В некоторых реализациях объектных баз данных может существовать набор баз данных, ко-
ординация и управление которыми выполняется централизованно. В этой книге такой набор баз данных называется группой баз данных (database group). На рис. 2.2 показана группа баз данных, которая включает четыре отдельные базы данных, содержащие объекты. Обычно на любой объект группы может ссылаться любой объект той же группы баз данных, как показано на рис. 2.2. Каждая фирма-производитель использует собственный термин для группы баз данных, например федеративная база данных (federated database) и сеть баз данных (database network).
Группа баз данных
Управление проектамиПродажи
Рис. 2.2. Группа баз данных
Весь доступ к объектам базы данных реализуется в контексте транзакции. Транзакция инициируется процессом приложения, который выполняет логическую группу операций с объектами базы (баз) данных. Эта группа операций обрабатывается как одна атомарная операция, т.е. выполняются либо все операции этой группы, либо ни одна из них (помните: “или все, или ничего”?).
Некоторые приложения для выполнения нескольких программных последовательностей внутри одного процесса используют потоки (поддерживаемые стандартом ODMG 2.0). Поток может быть связан не более чем с одной транзакцией одновременно, но транзакция может быть связана с несколькими потоками. Следовательно, приложение может иметь несколько параллельных транзакций. Каждый поток может иметь собственную транзакцию, или же несколько потоков могут взаимодействовать в контексте одной и той же транзакции. Для потока также возможно переключение с одной транзакции на другую. Существует много ситуаций, когда потоки оказываются весьма полезными для приложения.
При доступе к объектам базы данных со стороны приложения или при завершении транзакции могут возникнуть некоторые проблемы. Например, может отказать оборудование или нарушиться связь с другими процессами или компьютерами. Может также исчерпаться ресурс, необходимый для обеспечения работы базы данных, например память или дисковое пространство. Кроме того, может возникнуть конфликт блокировок, если два или больше приложений попытаются получить доступ к одним и тем же данным.
Программа, обеспечивающая функционирование базы данных, должна каким-то образом уведомлять приложение о возникновении проблемы. Ошибочные ситуации представляются объектами исключений (exception). В интерфейсе ODMG используются стандартные механизмы C++ для обработки исключительных ситуаций, или исключений.
Обо всем этом и пойдет речь в настоящей главе. В следующих разделах будут представлены интерфейсы ODMG для баз данных, транзакций и исключений. Глава завершится рассмотрением законченного приложения, в котором реализованы упомянутые интерфейсы.
flocmgn к базе данных
Стандарт ODMG предоставляет спецификацию для доступа к одной базе данных. Существуют реализации, которые способны поддерживать доступ к нескольким базам данных, но стандарт этого не требует.
Прежде чем приложение сможет получить доступ к объектам базы данных, нужно открыть базу данных. Каждая база данных идентифицируется по имени. Класс ODMG d_Database имеет функцию-член open, которой передается имя базы данных, подлежащей открытию. Для задания режима доступа используется перечисление access_status.
Предположим, приложение поддерживает информацию о зарплате в корпоративной базе данных с именем Personnel. Чтобы открыть эту базу данных, достаточно выполнить следующий фрагмент программы.
d_Databa.se db;
db.open("Personnel");
У функции open есть второй параметр типа access _status, причем по умолчанию для него используется значение d_Database: : read_write. Чтобы предотвратить возможный доступ к базе данных по учету кадров со стороны другого приложения, когда это приложение уже открыто, нужно в явном виде задать режим доступа d_Database::exclusive.
d_Database db;
db.open("Personnel", d_Database::exclusive);
Экземпляры класса d Database не являются перманентными, и они должны объявляться приложением. База данных должна быть открыта до начала транзакции. По завершении транзакций базу данных нужно закрыть, вызвав функцию-член close.
Транзакции
Транзакция имеет несколько свойств, которые можно обозначить аббревиатурой ACID. Транзакция базы данных должна поддерживать каждое из этих свойств:
атомарность (atomicity);
непротиворечивость (consistency);
изолированность (/solation);
долговечность (//inability).
Атомарность требует, чтобы в базе данных были отражены либо все операции, включенные в транзакцию, либо ни одна из них. Зачастую эти операции взаимозависимы, и выполнение лишь некоторых из них часто подвергает риску всю транзакцию.
Непротиворечивость означает, что транзакция должна осуществлять перевод базы данных из одного состояния в другое, сохраняя ее семантическую и физическую целостность. Реализация базы данных должна обеспечивать поддержку непротиворечивости на физическом уровне и гарантировать атомарность. Большинство правил семантики, необходимых для определения непротиворечивости данных, не могут быть выражены непосредственно в спецификации базы данных; семантикой напрямую должно управлять приложение. Именно приложение должно взять на себя ответст-
венность за поддержку семантической целостности объектной модели. Оно не должно изменять объекты такими способами, которые могут перевести их в несогласованное состояние. Предотвращению подобных изменений способствует применение инкапсуляции при определении классов.
Изолированность требует, чтобы каждая транзакция была единственной, контактирующей в данный момент с базой данных, даже несмотря на возможную параллельную работу других транзакций. Для транзакции не должны быть видны промежуточные результаты других еще незавершенных транзакций. Это связано с тем, что каждая транзакция должна быть атомарной. Если транзакция “видит” подмножество обновлений другой транзакции, то в этом случае (благодаря наличию взаимозависимостей между обновлениями) нельзя говорить о непротиворечивости представления базы данных. Причем другая транзакция также может прерваться, и тогда ни одно из обновлений, к которым был совершен доступ, не станет частью завершенного состояния базы данных.
Самый высокий уровень изолированности достигается в случае, когда все транзакции, выполняемые параллельно, можно осуществить последовательно. Такое состояние возможно, если содержимое базы данных, полученное в результате выполнения набора параллельных транзакций, совпадает с содержимым, являющимся результатом транзакций, выполненных последовательно.
Высокий уровень изолированности способен ограничить степень параллельности, которая может быть достигнута. Приложение, которое может позволить пониженный уровень изолированности в обмен на более высокую степень параллельности, способно допустить грязное чтение. В этом случае транзакция получает доступ к модификации, выполненной другой транзакцией!, которая еще не завершилась. Другая транзакция может прерваться, и тогда полученное из базы данных значение не будет отражать согласованное состояние. Именно природа приложения определяет приемлемость пониженного уровня изолированности.
Долговечность означает устойчивость завершенных транзакций перед угрозой возникновения сбоев. Если транзакция успешно завершена, обновления гарантированно будут отражены в базе данных, несмотря на потенциальные сбои, которые могут произойти после ее завершения. Долговечность подразумевает, что после сбоя в системе или средствах связи база данных может быть восстановлена. Состояние базы данных должно отражать обновления всех транзакций, которые успешно завершились до возникновения отказа.
Класс djransaction
Класс ODMG с Transaction обеспечивает типичный набор операций транзакции. Подобно классу d_Database, его экземпляры не могут быть перманентными. Они служат для группирования набора операций, инициированных приложением. Все операции, выполненные с перманентными объектами, должны быть заключены внутри транзакции. Для последовательных транзакций можно снова и снова использовать один и тот же экземпляр класса d_Transaction.
Транзакция начинается с обращения к функции begin после создания экземпляра класса d_Transaction. Имейте в виду, что конструктор не начинает транзакцию автоматически.
d_Transaction tx; tx.begin();
II Выполняем операции над объектами в базе данных...
tx.commit();
При вызове функции commit все перманентные объекты, которые были созданы, удалены или модифицированы во время транзакции, фиксируются в базе данных. Все объекты удаляются из кэша приложения, а все установленные транзакцией блокировки освобождаются. Если приложение предпочитает сохранить объекты и связанные с ними блокировки в кэше и при этом закрепить текущие изменения в базе данных, необходимо вызвать функцию checkpoint.
При вызове функции abort освобождаются все установленные транзакцией блокировки, но при этом ни одно из изменений не применяется. Если транзакция активна и при этом для ее экземпляра класса d_Transaction вызывается деструктор, то она прерывается. Это означает, что транзакция прерывается, если экземпляр класса d_Transaction является локальной переменной и возврат из функции организован без обращения к функции commit. Такое происходит и в том случае, если объект класса d_Transaction объявлен в функции, расположенной между функцией, генерирующей исключение, и функцией, перехватывающей его.
Двухфазное завершение
Если в приложении предусмотрена транзакция, которая манипулирует объектами в нескольких базах данных, то каждая база данных должна иметь возможность успешно завершить эту транзакцию. Если одна или несколько баз данных не могут это сделать, то они, возможно, будут оставлены в несогласованном состоянии.
Процесс двухфазного завершения используется для гарантирования того, что все базы данных, участвующие в транзакции, обновятся надлежащим образом. По сути, в такой транзакции существует субтранзакция. Согласно протоколу двухфазного завершения, каждая база данных должна определить, смогут ли завершиться ее обновления. Для индикации этой фазы часто используется команда prepare. Если в результате ответа серверов баз данных окажется, что все их субтранзакции могут успешно завершиться, то во второй фазе процесса завершения каждому серверу базы данных отдается распоряжение завершить транзакцию. Если любая из баз данных во время выполнения команды prepare сталкивается с некоторой проблемой, транзакция прерывается полностью. Таким образом, протокол двухфазного завершения гарантирует, что если невозможно завершить все операции транзакции, не будет завершена ни одна из них.
Потоковые операции
В приложении может быть несколько потоков, получающих доступ к базе данных, но каждый поток должен быть связан только с одной транзакцией. В классе d_Transaction определена функция join, которая соединяет текущий поток с транзакцией. Поток можно отсоединить от транзакции, вызвав функцию leave. Если функция join вызывается для экземпляра А класса d_Transaction, но текущий поток уже связан с другим экземпляром В класса d^Transaction, то, прежде чем поток будет соединен с экземпляром а, для экземпляра в неявно выполняется операция отсоединения. Поток в состоянии определить, какая транзакция соединена с ним в данный момент, вызвав функцию current, определенную в классе d_Transaction.
Программа базы данных обеспечивает каждую транзакцию в приложении собственным контекстом транзакции, как если бы она (транзакция) была инициирована в адресном пространстве отдельного процесса. Для каждого объекта класса d_Transaction в приложении сохраняются все ACID-свойства транзакции. Однако в некоторых реализациях для каждой действующей транзакции предусмотрена отдельная кэш-область. Ваше приложение не должно позволять потокам различных транзакций совместно использовать какие-либо объекты баз данных, поскольку при использовании разных продуктов результаты, по всей вероятности, будут различными.
Исключения: пОраОошка отказов
В ODMG-стандарте используются механизмы обработки исключений, определенные в языке C++. При возникновении ошибки генерируется экземпляр ODMG- класса d_Error, выведенного из класса exception, который определен в библиотеке стандарта ANS1/ISO для языка C++. В этом экземпляре содержится информация, описывающая данную ошибку. Каждая отдельная ошибка имеет номер, который называют видом (kind) ошибки. Тип данных kind, используемый для вида ошибки, определен в классе d_Error. Здесь же определены и функции, предназначенные для установки и чтения вида ошибки. В приложении Б приведено полное описание класса d_Error и рассмотрены все виды ошибок.
Вы можете предоставить дополнительную информацию об ошибке, вызвав одну из нескольких функций operator<<, которые присоединяют нужную информацию к строке. Эти функции принимают в качестве параметра значение, которое может иметь тип символа, целого и пр. (полный их список представлен в приложении Б). Приложение может вызывать эти функции неоднократно. При каждом перехвате и регенерации исключения обработчик приложения сможет добавлять нужную информацию.
К строке, содержащей информацию об ошибке, можно получить доступ, вызвав виртуальную функцию-член what, определенную в базовом классе exception. Эта функция определена в стандарте ANSI/ISO для языка C++.
Полное приложение, демонстрирующее транзакцию
Следующая функция main иллюстрирует все этапы открытия базы данных, начала транзакции и перехвата любого исключения, которое может возникнуть.
#include <odmg.h> d_Database db;
mainfint argc, char *argv[J) {
int ret ;
d_Transaction tx;
try {
db.open("Personnel"); tx.begin ();
// Выполнение транзакции tx.commit();
db.close ();
)
catch(d_Error serr) { cerr << "DB error " << err.get_kind() << " "; cerr « err.what() « endl; ret = -1;
)
catch(exception se) { cerr « "exception " « e.whatO « endl; ret = -2;
}
catch(. . . ) { cerr « "unknown exception\n";
} return ret;
}
Блоки try и catch, используемые приложением для обработки ошибок, обычно содержатся в функции main, но операции, связанные с открытием базы данных и транзакциями, могут выполняться в других функциях, вызываемых функцией main. В данном примере (для краткости и в целях демонстрации) они помешены в функцию main. Разработчик волен сам определить, где разместить блоки try и catch.
В стандарте ODMG не указаны имена заголовочных файлов. В этой книге предполагается, что файл с именем odmg.h содержит все необходимые объявления ODMG. Каждая фирма-производитель для своей реализации ODMG предоставляет имена собственных заголовочных файлов. Было бы неплохо локализовать эти различия в заголовочных файлах своего приложения и включить необходимые заголовочные файлы для реализации конкретного производителя продукта.
Глава 3
Моделирование объектных баз данных
Объектная парадигма предлагает богатый набор понятий дтя моделирования проблемной области. Инкапсуляция, наследование и полиморфизм предоставляют ряд преимуществ. которые облегчают разработку во время всего жизненного цикла проекта. Объектная база данных, со своей стороны, обеспечивает долговременное хранение объектов и координирует параллельный доступ. Разработка приложения только выиграет, причем значительно, если использовать для базы данных и приложения одну и ту же парадигму моделирования.
Приложения C++ не обязаны использовать именно объектную базу данных. Они могут быть рассчитаны на другую технологию, например реляционную. Но в этом случае база данных не будет иметь информации, связанной с объектным моделированием. Она будет “знать” только об объектной реализации, которая находится на более низком уровне абстракции. При этом разработчикам придется преобразовывать типы данных из объектной модели приложения в модель и систему типов базы данных. Поддержка обеих моделей и преобразования между ними — это серьезное бремя, которое ложится на плечи разработчика.
Объектные базы данных для определения объектных моделей и типов данных поддерживают возможности языка C++, а объявления классов приложения служат в качестве определения схемы базы данных. В этом случае приложению нужно определить только одну-единственную модель в одном-единственном языке, который будет использован как приложением, так и базой данных.
В настоящей книге предполагается, что читатель знаком с объектным моделированием и языком C++. Некоторые темы представлены здесь с точки зрения базы данных вообще, а не какой-то конкретной, поэтому их описание может слегка отличаться от описания в документации объектного проекта.
Здесь для объяснения принципов моделирования объектной базы данных в качестве базовой используется модель данных типа сущность-связь (entity-relationship — ER). Она применяется в данной индустрии как базис для объектных методологий, а также проектов схем баз данных. Большинство разработчиков, вероятно, уже знакомы либо с ER-моделью, либо с одной из объектных методологий, хотя для понимания материала это необязательно.
В данной главе вводятся конструкции ER-модели данных. Объектная модель включает много видов объектов. Часто они связаны с одной из конструкций ER- моделирования, которые служат для описания нескольких категорий типов. Определен-
ная грань поведения типа выводится из его членства в конкретной категории типов. Эта глава завершается рассмотрением абстракции, которая должна использоваться типами в каждой категории типа для обеспечения атомарного интерфейса и сокрытия сложностей, возникающих в результате различных объединений в реализации класса.
Модель данных вида сущность-связь
В 1976 году Питером Ченом (Peter Chen) была введена ER-модель данных, которая долго оставалась одной из самых популярных моделей данных приложения. Но с годами эта модель была усовершенствована. После признания объектноориентированной технологии ER-модель данных была расширена за счет включения понятия объекта. Примитивы объектного моделирования в большинстве объектных методологий выводятся преимущественно из ER-модели.
ER-модель может облегчить создание проекта логическом и физической схем базы данных. Логическая схема определяет сущности, атрибуты и связи проблемной области, в то время как физическая связана с представлением в памяти и механизмами доступа. ER-модель используется приложениями, в которых применяются иерархические, сетевые и реляционные технологии баз данных. Эта модель представляет собой прочный фундамент, на котором можно построить (определить) модели данных приложения, причем способ определения в основном не зависит от технологии базы данных.
ER-модель используется как для моделирования объектов в приложении, так и проектирования схемы базы данных. Более того, с использованием объектной базы данных объектная модель приложения уже сама по себе является спецификацией схемы базы данных. Таким образом, принципы ER-модели обеспечивают общий фундамент для описания моделей объектных баз данных.
Примитивы ER-моделирования
ER-модель приложения включает такие понятия, как
сущность;
связь;
атрибуты.
Для моделирования этих конструкций ER-модели можно использовать объекты.
Сущность (entity) — это предмет, который необходимо смоделировать. Определения сущностей и объектов взаимосвязаны. В литературе по ER-моделированию сущность описывается как объект, а в литературе по объектному моделированию объект рассматривается как сущность в проблемной области. Словарь Вебстера (Webster) описывает и сущность, и объект как “предметы", а в словаре Роджета (Roget) понятия сущность, объект и предмет являются синонимами. Новички в объектной технологии часто спрашивают: “Что такое объект?’’. Ответ следует искать в исследовании “предметов”, которыми должно управлять приложение и которые часто оказываются объектами, составляющими “материальную базу” моделирования: люди, города, неодушевленные объекты, понятия, события и т.д. Сущности представляют собой самую распространенную форму объекта, моделируемую в приложениях.
Связь (relationship) — это отношение между двумя или более сущностями. Важно понимать, как именно взаимосвязаны сущности, поскольку большая часть системы посвящена поддержке таких связей. В качестве примеров можно привести отношения между супругами, родителями и детьми, работодателем и служащим или. связь между продуктом и его составными частями. Для представления связей также используются объекты.
Как сущности, так и связи могут иметь атрибуты', свойства или характеристики, связанные с сущностью и связью. Атрибут имеет имя и тип. Тип атрибута называют доменом (domain). К простым доменам относятся такие типы, как целые и строки. Сущность Person (человек) может иметь атрибуты, предназначенные для представления имени и адреса.
В качестве доменного типа может служить и класс. Например, экземпляр класса Date можно использовать для указания даты рождения сущности Person. Каждый домен представляет конкретную абстракцию, а его интерфейс должен обеспечить набор операций, поддерживающих эту абстракцию (например, набор операций, обычно выполняемых с датами).
Как правило, ER-модели фокусируются только на атрибутах данных. В объектных моделях больше внимания уделяется операциям интерфейса, представляющего абстракцию. Атрибуты данных играют вторичную роль, поскольку они обычно инкапсулированы в реализации и не являются доступными компонентами в объектном интерфейсе. В модели объектной базы данных должны быть определены как атрибуты, так и операции.
Сходство в моделях баз данных
Все технологии баз данных (сетевая, реляционная и объектная) используют различную терминологию для механизмов, обеспечиваемых ими для представления ER- моделей. В табл. 3.1 приведены конструкции и термины, используемые в каждой технологии. Разобравшись в этой таблице, вы сможете применить знание одной технологии к другим. Эта таблица соответствия также пригодится, когда вам придется переводить конкретную модель из одной технологии баз данных в другую.
Таблица 3.1. Сходство в технологиях баз данных
Сущность-связь Технология баз данных
Иерархическая/ сетевая
Реляционная
Объектная
Сущность (тип)
тип записи
таблица
класс
Хранение сущности
файл
таблица
экстент
Конкретная сущность
запись
строка/кортеж
экземпляр
Идентификатор сущности
первичный ключ
первичный ключ
ID объекта
Атрибут
поле
столбец
член
Связь
связь
внешний ключ
объектная ссылка
Категории типов
Каждая сущность, атрибут и связь в ER-модели приложения представлены типами. Когда применяется объектно-ориентированная парадигма, многие из этих типов являются объектами. Рассмотрим рис. 3.1, который иллюстрирует инкапсуляцию объекта. Объект обеспечивает набор операций, которые представляют его абстракцию. Эти операции изолируют пользователей объекта от внутренней реализации, которая зачастую является объединением компонентов данных.
Каждый тип, будь то объект или примитивный тип (например, int), устанавливает для своих подкомпонентов некоторый контекст. Например, такой простой тип, как целый, состоит из объединения битов. Для манипуляции над битами определяются операции, причем это делается таким способом, который поддерживается целочисленной абстракцией. Значение экземпляра типа выводится из совокупных значений его компонентов. Достоверность значения компонента часто базируется на роли компонента в поддержке значения включающего типа.
Рис. 3.1. Инкапсуляция объекта
Значения компонентов часто взаимозависимы, и понять эти взаимосвязи иногда довольно сложно. Включающий тип должен поддерживать значения собственных компонентов. Логика, необходимая для этой задачи, должна быть локализована в операциях, определенных для этого типа. Например, класс, представляющий доменный тип даты, включает операции, которые реализуют категорию даты, благодаря чему мы можем выполнять с датами арифметические операции, сравнивать их и т.д. Программа, которая использует класс даты, не нуждается в понимании того, как дата представляется внутренне (т.е. не интересуется ее внутренними компонентами и взаимосвязями). Термин ограничение семантической целостности относится к механизмам, которые поддерживают такие взаимозависимости при модификации данных, а для поддержки семантической целостности парадигма объектов использует инкапсуляцию. Инкапсуляция уменьшает сложность за счет локализации и минимизации программного кода, который требуется для понимания и поддержки взаимозависимостей компонентов.
Объектные модели приложений состоят из набора типов, и каждый тип обычно играет роль одной из ER-конструкций. Каждая ER-конструкция состоит из объединения компонентов. Компоненты домена обычно представляют собой один или несколько примитивных типов языка C++, хотя любой домен может содержать экземпляры классов других доменов. Класс обычно определяется для каждой сущности в объектной модели и включает один или несколько атрибутов, причем каждый атрибут имеет конкретный доменный тип. Составная сущность содержит в качестве своих компонентов экземпляры других сущностей.
Каждая конструкция ER-моделирования представляет категорию типов. Некоторые функции типа являются следствием его “социальной” роли: сущности, связи или домена. Определенные характеристики подходят для каждого типа, представляющего домен; это также справедливо для сущностей, коллекций и составных сущностей. Различные категории типов кратко рассматриваются в следующих разделах, а подробное описание каждой категории приводится в отдельных главах. Каждая категория типов содержит операции, которые поддерживаются всеми ее типами. Эти операции представляют, если говорить метафорически, базовые транзакции, которые выполняются приложениями при манипуляции экземплярами типа.
Сущности
В большинстве объектных методологий акцент ставится на проектировании объектов, которые представляют сущности в проблемной области. В объектной базе данных объект сущности “материализуется" в виде некоторого узла (или блока), которому присущи как свойство перманентности, так и возможность идентификации. В качестве примеров сущностей можно привести классы Gift и GiftList из главы 1. Система резервирования (бронирования) авиарейсов будет иметь дело с сущностями самолетов, аэропортов, пассажиров и рейсов. Для каждой из этих сущностей будет определен свой класс.
Класс с “генами перманентности" (persistence-capable class) может иметь экземпляры, которые хранятся в базе данных и к которым возможен доступ со стороны нескольких процессов приложений. Экземпляры перманентных классов также могут быть транзитными (временными), и это значит, что они существуют только внутри одного процесса приложения. В литературе, посвященной объектным базам данных, и документации фирм-производителей объекты сущностей обычно именуются просто “объектами" или “ перманентными объектами”.
Каждый перманентный экземпляр класса сущности имеет уникальный идентификатор объекта, который используется в базе данных для ссылки на этот экземпляр или доступа к нему. Идентификатор объекта отделен от атрибутов. Его отличительная черта — постоянство, т.е. приложение его изменить не может. Перманентные экземпляры иногда называют указуемыми объектами (denotable objects), т.е. на них можно указывать (ссылаться) с любого “места” базы данных. Именно постоянство и наличие идентификатора отличают классы сущностей or типов в других категориях типов. Идентификаторы объектов рассматриваются в главе 6.
Свойства сущности (атрибуты. связи и операции) реализуются данными и функциями-членами класса. Атрибут каждого данного имеет имя и является экземпляром конкретного типа данных. Аналогично каждая операция представляет собой функцию-член с именем, параметрами и типом возврата. Сущность должна управлять модификациями значений ее атрибутов, которые могут зависеть от значений других атрибутов в этой сущности. Класс, представляющий сущность, ответствен за поддержку полного абстрактного значения. В своей реализации класс устанавливает контекст, который определяет действительные значения для атрибутов. Сущности описываются в главе 5.
Домены
Термин домен (domain) относится к категории типов, которые служат в качестве типов данных для атрибутов и типов возврата функций. Атрибут не имеет идентификатора объекта и на него нельзя напрямую ссылаться в базе данных. Он приобретает качество постоянства путем встраивания в виде члена внутри перманентного объекта.
В литературе по объектно-ориентированному программированию практически не используется термин домен применительно к типу данных атрибута, но это определение широко применяется в литературе по базам данных. Термин домен также распространен в теории множеств и абстрактной алгебре, и именно эти разделы служат теоретической основой для многих аспектов технологии баз данных.
Домен может быть либо единственным примитивным типом языка C++ (например, как int), либо классом, составленным из собственного набора атрибутов. Фирма-производитель базы данных может предоставить набор таких полезных доменов, как дата, время, деньги и т.д. Разработчики могут также определить специфические для данного приложения домены для представления таких категорий данных, как телефонные номера, номера документов социального обеспечения и растровые изображения.
В теории нормализации реляционных баз данных связь существует в первой нормальной форме только в том случае, если все лежащие в ее основе домены являются атомарными, т.е. несоставными. Любое объединение в реализации невидимо в интерфейсе. Каждая абстракция в объектном проекте должна быть атомарной. Например, класс имеет как интерфейс, так и реализацию, которая часто представляет собой объединение нескольких компонентов. Однако интерфейс класса, представляя определенную абстракцию, должен скрывать такую сложную реализацию. Даже такой простой домен, как целочисленный тип, является объединением битов. Целочисленная абстракция, обеспечиваемая языком, атомарна, несмотря на то что лежащая в ее основе реализация таковой не является.
Инкапсуляцию можно использовать для перевода нестандартного (определенного пользователем) типа домена в атомарный, даже если его реализация представляет собой некоторое объединение. Эта идея определяет основную тему настоящей книги. Она применяется к типам в каждой категории типов и не касается одних лишь доменов. О доменах речь пойдет в главе 4.
Коллекции
Коллекция представляет набор значений (количество которых может быть равно нулю) некоторого типа. Списки, массивы и множества — это примеры распространенных коллекций. Коллекция представляется классом, который параметризирован на основе типа элемента, содержащегося в нем. Для определения классов коллекций используется такое средство языка C++, как оператор шаблона Template. Параметр шаблона как раз и означает тип элемента в коллекции.
Коллекции играют существенную роль в объектных базах данных. Например, атрибут может состоять из коллекции значений некоторого типа. Коллекции также используются для представления связей (отношений) между сущностями с кардинальным числом, превышающим единицу (один-ко-многнм и многие-ко-многим). Коллекции также служат в качестве способа доступа ко всем экземплярам класса. Коллекции, определенные в стандарте ODMG, обладают способностью к перманентности. Они могут быть независимыми (автономными) перманентными объектами, обладающими идентификаторами, или же они могут встраиваться в объект сущности в качестве типа домена для атрибута. О коллекциях речь пойдет в главе 7.
Составные сущности
Следует выделить еще одну категорию типа — составной объект (composite object), или составная сущность (composite entity). Некоторые авторы называют этот тип сложным объектом (complex object). Отличительная особенность составной сущности — наличие в ее реализации набора или объединения других объектов сущностей. Эти объекты называются подобъектами (subobject) и сами могут быть составными сущностями. В некоторых объектных методологиях они именуются иерархиями объектных включений (object containment hierarchies).
Составная сущность управляет существованием и взаимозависимостями своих подобъектов, отсюда и термин: зависимо существующие (existent-dependent) компоненты составной сущности. Это значит, что при удалении экземпляра составной сущности удаляются и все его подобъекты. Составные сущности рассматриваются в главе 9.
Составные сущности широко используются в CAD/CAM-приложениях, проекты которых содержат много взаимосвязанных подкомпонентов. Например, если бы понадобилось создать объектную модель самолета Boeing 747, то она бы состояла из экземпляров многих классов. Самолет можно было представить экземпляром составной сущности с именем Plane, а надлежащие операции определить как функции-члены в
классе Plane. Такие сборочные узлы, как крыло, кабина или фюзеляж, следовало бы представить в виде экземпляров составных сущностей, состоящих из набора взаимосвязанных компонентов. Очень часто для представления сложного проекта необходима сложная иерархия компонентов. Управление взаимозависимостями между компонентами — очень важный аспект работы любого приложения.
Объединение, абстракция и атомарность
В этой книге основное внимание уделяется связям между объединением, абстракцией и атомарностью, т.е. их отношениям к таким категориям типов, как сущность, домен и составная сущность.
Составная сущность представляет собой объединение сущностей. Сущность является объединением атрибутов определенных доменов, а домены — это объединения других типов. Типы в каждой из этих категорий представляют некоторую абстракцию, а реализация абстракции состоит из объединения компонентов. Объединение и взаимозависимости компонентов создают сложность. Инкапсуляция уменьшает сложность и обеспечивает интерфейс для типа, который соответствует данной абстракции. Операции в интерфейсе типа должны скрывать объединение в реализации.
Рис. 3.2. Иерархия включений категорий типовРис. 3.2. Иерархия включений категорий типов
Рис. 3.2. Иерархия включений категорий типов
Рис. 3.2. Иерархия включений категорий типов
На рис. 3.2 показан процесс образования сущностей из доменов и составных сущностей из доменов и сущностей на основе их объединения и включения одних компонентов в состав других. Составные сущности представлены прямоугольниками с закругленными углами. Их компоненты могут состоять из сущностей и других составных сущностей. Составные сущности предоставляют набор операций, используемых для управления взаимозависимостями между элементарными сущностями, которые представлены кругами. Компоненты элементарных сущностей состоят из экземпляров доменов, представленных на рисунке различными геометрическими фигурами, означающими типы, и помеченных буквой Д. Типы на каждом уровне иерархии должны использовать инкапсуляцию для скрытия их базовой реализации, что уменьшает сложность модели в целом.
Глава 4
Домены
Домены представляют собой самые примитивные типы, сохраняемые в базе данных. Сущности и связи обычно имеют набор атрибутов, а домены служат в качестве типов данных для этих атрибутов. Каждый атрибут — это экземпляр данного типа домена. Типы доменов обычно неспособны к постоянству, хотя существуют исключения. Экземпляр типа домена приобретает свойство перманентности после внедрения его в перманентный объект. В схеме объектной базы данных в качестве домена могут служить различные типы. Разработчики приложений, используя объектные базы данных. могут определять собственные типы доменов, т.е. они не ограничены доменами, поддерживаемыми фирмой-производителем конкретной базы данных.
В настоящей главе описаны ограничения других технологий баз данных, связанные с поддержкой доменов, необходимых приложениям. Здесь также рассматриваются характеристики и категории доменов. Наконец, приводится описание доменных типов стандарта ODMG и рассматриваются типы языка C++, которые не поддерживаются реализациями объектных баз данных.
Домены в других технологиях баз данных
В других технологиях баз данных (иерархических, сетевых и реляционных) обеспечивается фиксированный набор доменов. Чаще всего поддерживаются следующие домены:
целое число;
десятичное число;
число с плавающей точкой;
символьная строка (фиксированной и переменной длины);
битовая строка (фиксированной и переменной длины);
дата;
время;
дата и время;
двоичный объект большого размера (binary /arge object — BLOB).
Эти типы данных поддерживаются в стандарте SQL92. Атрибуты в объектной модели приложения должны быть представлены одним из типов, обеспечиваемых производителем. Приложение, опирающееся на SQL-базу данных и испытывающее необходимость в использовании типа с другой семантикой или представлением, должно разложить этот тип на один или несколько примитивных типов. Некоторые сложные атрибуты используются в виде нескольких строк во вторичной таблице, а для доступа к этим строкам применяется операция соединения.
Функциональные возможности этих типов данных сосредоточены в реляционном сервере. Язык, используемый приложением, обычно имеет собственные типы, которые соответствуют типам базы данных. Фирма-производитель базы данных, как правило, обеспечивает преобразование между типами базы данных и соответствующими типами данных в приложении. Операции, выполняемые над доменными типами базы данных, выражаются в языке SQL и выполняются только в сервере реляционной базы данных. Для манипуляции экземплярами доменов в приложении используются возможности встроенных типов языка приложения. Но вся беда в том, что типы базы данных и приложения часто обладают различными функциональными возможностями, что способствует повышению уровня сложности приложения.
Некоторые типы базы данных (например, дата и время) не имеют соответствующих типов данных в приложении. Поэтому приложение должно извлекать значения определенной абстракции (месяц, день и год), чтобы затем их можно было представить с помощью типов данных приложения. В приложении определяется представление для типа базы данных, и эти значения используются для инициализации экземпляра.
В приложениях может возникнуть необходимость в представлении типа, который не поддерживается базой данных. В этом случае его нужно создать в дополнение к основным типам, поддерживаемым базой данных; кроме того, должно быть определено преобразование между двумя разными представлениями. При этом преобразование и соответствующая семантика будут известны только программному обеспечению приложения; база данных не моделирует абстракцию приложения, поскольку не может обеспечить интерфейс с помощью таких ее инструментов, как языки запросов и генераторы отчетов. Даже несмотря на то. что приложение может определить богатую объектную модель и систему типов, пользователю языка запросов придется работать на более низком уровне реализации, который не отражает соответствующих абстракций в объектной модели. Нечеткость семантики приведет к значительному усложнению схемы базы данных. Эту ситуацию можно сравнить с написанием программ на C++ и необходимостью последующей ее отладки на уровне языка Assembler.
Фирмы-производители реляционных баз данных пытаются решить проблемы представления некоторых типов данных путем поддержки BLOB-типов данных. Этот тип обеспечивает хранение двоичных данных переменной длины. Приложения обычно используют тип BLOB для хранения растровых изображений, аудио-, видео- и графических данных. Тип BLOB также может применяться в качестве “накопительного бункера” для ненормализованных данных, которые не могул быть эффективно представлены в реляционных таблицах. С этим типом не связываются никакие семантические правила: данные должны интерпретироваться самим приложением. Для типа BLOB необходимо поддерживать только две операции: получение и установка его значения.
Использование типов BLOB вносит различные ограничения. Их значения потенциально огромны. Если обновления BLOB регистрируются в журналах транзакций, то эти журналы быстро превышают свои возможности. Поэтому некоторые системы не регистрируют изменения значений BLOB-типов в журналах транзакций, что приводит к потере семантики транзакций. Разработчики приложений часто идут по другому пути. Приложения могут сохранять данные прямо в файловой системе, а в базе данных запоминается только имя файла. Но в таком случае ответственность за обеспечение свойств транзакций ложится на разработчиков. В некоторых системах баз данных раз-
решается создавать только по одному BLOB-столбцу в таблице, а это вызывает существенные неудобства, если некоторая сущность имеет несколько атрибутов, которые лучше всего представлять именно типом BLOB. В такой ситуации приходится создавать дополнительные таблицы (по одной для каждого BLOB-значения).
Категории доменов
Категории доменов базируются на различных уровнях объединения и инкапсуляции.
Примитивные литеральные типы
Составные типы
Абстрактные типы данных
Этим категориям и соответствующим типам ODMG и посвящен данный раздел. Объектные базы данных C++ позволяют большинству типов, поддерживаемых в C++, быть доменами.
Все домены имеют реализацию, которая представляет собой объединение компонентов — либо битов, либо экземпляров других доменов. Тип, представляющий дату, считается атомарным, несмотря на то что содержит несколько компонентов — год, месяц и день. Аналогично строка считается доменным типом, несмотря на то что она представляется массивом символов. Эти компоненты являются значениями некоторой абстракции; при этом представление ее реализации может быть совершенно другим.
Правило первой нормальной формы реляционных баз данных гласит, что все домены должны быть атомарными, т.е. каждый домен должен представлять атомарную абстракцию. Соблюдение этого правила приветствуется и в объектном проектировании. При атомарном интерфейсе домена его реализация может быть неатомарной, если для сокрытия объединения реализации используется инкапсуляция. В приложениях (для выполнения определенных требований производительности) может возникнуть необходимость хранить данные в ненормализованной форме. Поэтому фирмы- производители реляционных баз данных вынуждены поддерживать хранение ненормализованных данных.
Примитивные литеральные типы
Язык C++ поддерживает следующий набор примитивных литеральных типов:
char (со знаком и без знака);
short (со знаком и без знака);
int (со знаком и без знака);
long (со знаком и без знака);
float И double.
Операции, выполняемые с экземплярами этих типов, обеспечиваются компилятором и системной средой. При определении нестандартных (определенных пользователем) операций для этих встроенных типов нельзя использовать инкапсуляцию.
Еще один примитивный тип данных C++, указатель, используется для хранения адреса памяти экземпляра конкретного типа. Указатели не поддерживаются в стандарте ODMG, хотя некоторые фирмы-производители их все же поддерживают. Одни производители позволяют запоминать указатели, которые ссылаются на перманентные объекты в базе данных; другие разрешают указателям ссылаться только на транзитные данные. Однако следует иметь в виду, что приложения, в которых используются атрибуты указателей, не переносимы между реализациями ODMG.
После того как была выпушена версия 1.2 стандарта ODMG, в язык C++ был добавлен булев тип данных bool. Этот тип еще не получил широкого распространения в реализациях компиляторов. Многие функции ODMG возвращают логическое значение (истинное или ложное), а в качестве типа для булева значения в версиях ODMG 1.0—1.2 используется тип int. Для представления ложного значения служит число 0, а для истинного — любое ненулевое целое значение. Это соглашение долгое время использовалось в языках С и C++. Но уже в ODMG версии 2.0 интерфейсы были изменены таким образом, чтобы разработчики могли использовать тип ODMG d Boolean, который имеет значения dJTrue и d_False.
.2.1.1. Примитивные типы OOMG
Стандарт ODMG поддерживает некоторое подмножество примитивных типов языка C++. В табл. 4.1 показаны определения (typedef), которые обеспечиваются в реализациях ODMG для поддерживаемых типов данных.
Таблица 4.1. Примитивные литеральные домены ODMG
Тип
Размер
Описание
d_Char
8 бит
Символ ASCII
d_Octet
8 бит
Без интерпретации
d_Boolean
He определен
d_Trueиd_False
d_Short
16 бит
Короткое целое со знаком
d_Ushort
16 бит
Короткое целое без знака
d_Long
32 бита
Целое со знаком
d_ULong
32 бита
Целое без знака
d_Float
32 бита
Число с плавающей точкой одинарной точности (стандарт IEEE 754-1985)
d__Double
64 бита
Число с плавающей точкой двойной точности (стандарт IEEE 754-1985)
Типы int и unsigned int не включены в интерфейс ODMG. Размер типа int зависит от размера слова в системе. В существующих архитектурах компьютеров для целых значений используются слова длиной в 16, 32 или 64 бита. Если вы работаете с объектной базой данных в мультикомпьютерной среде, в которой используется различная длина слов, то вряд ли сможете передавать между компьютерами значения типа int без потерь, поскольку в одних компьютерах может быть задействовано меньше бит, чем в других. Если предполагается, что ваше приложение никогда не будет работать в такой среде (для нашего времени это рискованное, знаете ли, предположение), то все-таки в некоторых реализациях возможно использование типа int.
Типы, определенные в табл. 4.1, берут свое начало из языка определения интерфейсов (Interface Definition Language — IDL) стандарта CORBA (Common Object Request Broker Architecture), описывающего взаимодействие объектов в сетевой среде. Эти типы имеют одинаковый диапазон и интерпретацию во всех платформах. Для достижения гетерогенности некоторые реализации ODMG требуют, чтобы в приложениях использовались именно эти типы, а не их С++-эквиваленты. Одни реализации позволяют использовать стандартные типы данных языка C++ (например, тип long), а другие — нет. Если приложения применяют типы ODMG, то они обладают большей переносимостью и у них больше шансов поддержки гетерогенности.
Составные топы
Составной тип содержит несколько данных-членов, к которым возможен прямой доступ. К составным относятся типы struct или class с открытыми (public) данны- ми-членами. Структура — это объединение данных-членов. которые группируются по некоторым семантическим факторам. Составные типы не используют инкапсуляцию.
Большинство С++-программистов вместо структур используют классы, чтобы иметь возможность применять инкапсуляцию и наследование. Но без структур нельзя обойтись, если нужно обеспечить интерфейс с программами, написанными на С. Структура сама может содержать вложенные встроенные структуры. Атрибуты могут быть вложенными на произвольную глубину, хотя в некоторых реализациях возможны определенные ограничения.
Массив C++, который объявляется как атрибут перманентного объекта, можно считать составным типом с индексированным доступом к его элементам. Массивы встраиваются в коллекции, обеспечиваемые языком программирования. Для работы с массивами используется стандартный синтаксис языка C++. Структура Address, определение которой приводится ниже, содержит несколько массивов символов. В некоторых реализациях объектных баз данных многомерные массивы не поддерживаются, и стандарт ODMG не требует такой поддержки.
Составными являются следующие типы.
struct Address {
charstreet [ 40 ];
charcity[20];
charstate[ 3] ;
charzip_code[6);
} ;
struct Point {
intx;
inty;
} ;
class Rectangle {
public:
Pointorigin;
intwidth;
intheight;
// операции } ;
Класс Rectangle содержит член типа Point, который является составным типом. Сам класс Rectangle считается составным типом, поскольку он содержит открытые данные-члены.
Абстрактные типы данных
Можно определить класс для реализации абстрактного типа данных (АТД), представляющего некоторую абстракцию. Слово “абстрактный” (abstract) означает не абстрактный базовый класс, как в языке C++, а определенный пользователем тип, который представляет абстракцию в проблемной области. Тип АТД обеспечивает операции, которые осуществляют представляемую абстракцию, поддерживая непротиворечивость ее значения при переходе от одного объекта к другому. Эти операции поддерживают семантическую целостность и внутреннюю непротиворечивость любого экземпляра.
Абстрактный тип данных отличается от составного, поскольку инкапсуляция подразумевает наличие интерфейса, который олицетворяет1 атомарную абстракцию. Реализация может быть сложным объединением компонентов, которое, однако, недоступно.
Типы АТД обеспечиваются либо базой данных, либо приложением. С помощью АТД обычно поддерживается тип даты. Приложения часто имеют собственные абстрактные типы данных, хранимые в базе данных и управляемые ею. Объектная база данных позволяет определить эти типы и использовать их в схеме.
Коллекции
Коллекция — это разновидность абстрактного типа данных, которая используется в большинстве объектных моделей. Для поддержки отдельных операций и общего поведения можно определить различные виды коллекций, которые отличаются по функциональным характеристикам. Тип коллекции следует выбирать, исходя из требований к применению. Большинство исходных библиотек C++ состояли из классов коллекций.
Коллекции могут служить в качестве доменного типа атрибута. Они также несут и другую нагрузку, имеющую отношение, например, к определению связей, доступу ко всем экземплярам класса и обработке запросов. Подробнее запросы рассматриваются в главе 7.
Встроенные перманентные ебъекты
В большинстве реализаций объектных баз данных для атрибута разрешено использовать любой тип данных, включая класс с “генами перманентности", используемый как домен. Однако в этом случае экземпляр теряет свой объектный идентификатор, и на него невозможна непосредственная ссылка из любой точки базы данных.
Другие примеры
В некоторых моделях приложений широко используется тип данных, применяющий двоично-десятичное представление чисел (binary coded t/ecimal — BCD). Еще один тип данных, вероятно, основанный на BCD-типе, удобно использовать именно для представления денежных сумм.
Приложения часто содержат собственные типы, которые неизвестны программам, входящим в систему управления базой данных. При использовании объектной базы данных они могут служить в качестве типа атрибута. Но другие технологии баз данных (так уж исторически сложилось) не допускали применения расширенной системы типов для доменов атрибутов. Такая гибкость — одно из преимуществ именно объектной базы данных.
4.3. Типы доменов ПОМЕ
В стандарте ODMG определяется набор классов для представления абстракций распространенных доменов. Эти классы поддерживают каноническую форму C++, которая включает действующую по умолчанию функцию инициализации (нулевой конструктор), конструктор копии, оператор присваивания и деструктор. Они также поддерживают специальные функции, необходимые для их абстракции.
Вероятно, каждая модель данных имеет строковые атрибуты. В стандарте ODMG определен строковый класс d_String, который поддерживает только минимальный набор операций, необходимых для хранения строк в базе данных. К сожалению, он не предназначен для использования в качестве обобщенного строкового класса с расширенным набором операций обработки строк. Скорее всего, требования рынка, заставят производителей поддерживать также строковые классы, определенные в стандарте ISO языка C++.
Стандарт ODMG поддерживает следующие классы для представления абстракций даты и времени (определения этих классов приведены в приложении Б).
d_Interval
d_Date
d_Time
d_Timestamp
Экземпляры этих классов доменов могут быть отдельными транзитными экземплярами или встроенными атрибутами перманентных объектов. В этой главе приводятся примеры их использования в качестве отдельных транзитных экземпляров, а перманентные объекты и их атрибуты описаны в главе 5. Поведение экземпляра не зависит от того, автономен он или встроен.
Эти классы были определены еще до выпуска библиотек стандарта ANSI/ISO C++, поэтому у них существует некоторое перекрытие. Типы ODMG реализованы в нескольких языках программирования. Рынок, вероятно, заставит производителей объектных баз данных поддерживать и классы, определенные в библиотеках ANSI/ISO C++.
Класс d_String
Класс d_String представляет строку переменной длины. Экземпляр класса d string может быть инициализирован символьным массивом, другим экземпляром класса или “ничем”, т.е. значением нулевой (пустой) строки.
d_String d_String d_String d_String d_String *d_String d_String d_String d_String d_String *nullstring; // нулевой конструктор composer("Mozart");
d_String d_String d_String d_String d_String *
d_String d_String d_String d_String d_String *
popArtist("Michael Bolton");
singer(popArtist); // конструктор копии favorite;
composer = "Beethoven"; // присваиваем с помощью char * favorite = new d_String("Richard Marx");
popArtist = ‘favorite; // присваиваем с помощью d_String
Класс d string не поддерживает напрямую интерфейсы ввода-вывода потоков C++, но операторы вставки в поток и извлечения из него можно определить следующим образом.
istream soperator» (istream sis, d_String ss) {
char buffer[256];
is.width(256); // ограничиваем 256 символами is » buffer;
s = buffer; // иллюстрирует присваивание символьного массива return is;
ostream Soperator« (ostream Sos, d_String ss) (
os « (const char *)s;
return os;
}
Как показано в определении operator«, приведенная функция преобразует экземпляр класса d_String в тип const char * для получения доступа к внутреннему значению строки.
Операторы сравнения выполняют сравнение экземпляра класса d string с другим экземпляром того же класса или с символьным С++-массивом. Функция-член при-
нимает длину строки, а оператор operator [] получает доступ к символу с заданным индексом в строке. Например, следующая функция заменяет каждую строчную букву прописной.
void makeUpperCase(d_String &s)
{.
register unsigned long i;
register const unsigned long len = s.length 0;
for( i = 0; i < len; i++) { char c = s [i];
if ( isalpha(c) && islower(c) ) s[i] = (char) toupper(c);
) )
Перманентные классы, которые требуют наличия строкового атрибута, могут использовать либо класс d_String, либо символьный массив. Символьный массив имеет фиксированный размер, в то время как экземпляр класса d_String содержит строковое значение переменной длины.
class Employee : public Person (
chargender[2]; //пол: "M" (муж.) или "F" (жен.)
d_Stringemail_address;
)"
В главе 5 атрибуты рассматриваются как компоненты типов сущностей.
Для поддержки строки произвольного размера в реализациях объектных баз данных содержимое этой строки хранится вне объекта, что приводит к незначительным затратам системных ресурсов. С помощью символьного массива данные напрямую встраиваются в объект, и в этом случае не приходится говорить ни о каких вторичных данных, которые необходимо считывать и записывать. Таким образом, если строковые значения невелики и не сильно отличаются по размеру, использование символьных массивов более предпочтительно.
Класс d_lnterval
Класс d Interval представляет промежуток времени, а также используется для поддержки арифметических операций совместно с другими классами, имеющими отношение ко времени. Экземпляр этого класса можно инициализировать значениями суток, часов, минут и секунд. Сутки — это самая большая единица времени, которая может быть задана с помощью класса d_lnterval, поскольку в разных месяцах или годах количество дней (суток) неодинаково. В следующей функции выполняется вставка компонентов интервала времени в поток вывода.
ostream &operator« (ostream &os, d_Interval &i) {
os << "Intervale << i.dayO « " days ";
os << i.hour() << " hrs ";
os << i.minute() « " mins ";
os << i.second() « " secs)";
return os;
}
На этом примере иллюстрируется использование функций, которые предоставляют отдельные компоненты полного значения абстракции. Чтобы выяснить, все ли компоненты времени равны нулю, можно вызвать функцию-член is_zero.
В классе d_Interval поддерживаются каноническая С++-форма и полный набор арифметических операций и операций сравнения. Класс d lnterval поддерживает ненормализованный ввод, выполняя нормализацию компонентов времени, когда они доступны. Например, после выполнения следующих строк программы все экземпляры класса d lnterval будут иметь значение, равное полутора суткам (36 часов).
d_
Interval
inti(1,
12) ;
//
1
день
12
часов
d
Interval
int2(inti);
//
1
день
12
часов
d_
Interval
int3(0,
36) ;
//
1
день
12
часов
d_
.Interval
int4(0,
24, 12*60);
//
1
день
12
часов
d
Interval
int5(0,
12) ;
//
12
часов
d_
Interval
int6 (6)
//
6
дней
d
Interval
int7 ;
//
все нули
d_
Interval
int8(0,
-12) ;
//
-1
2 часов
d
Interval
int9(2,
-12) ;
//
1
день
12
часов
if (
int5 < inti )
int5 *= 3;-
iff
inti == int2 ) int6 = int6 / 4;
if (inti + int2 == int3 * 2
int7 = inti * 2 - int2;
int8 = d Interval(6) - 3*int9;
В каждом операторе if выражение в скобках является истинным. Экземпляры класса d_Interval часто используются при выполнении арифметических операций над экземплярами классов d_Date и d_Timestamp.
Класс 11 Date
Класс d_Date поддерживает абстракцию даты, состоящую из года, месяца и дня. Если экземпляр этого класса инициализируется без аргументов, он имеет значение текущей даты. Статическая функция current возвращает экземпляр класса d_Date с текущей датой. Компоненты даты передаются конструктору следующим образом.
d_Date today; // экземпляр инициализируется текущей датой d_Date birthday(1959, 6, 16); // год, месяц, день
Предположим, как показано ниже, была сделана попытка инициализировать экземпляр запрещенным значением.
d_Date badDate(1996, 13, 2);
Эта попытка генерирует исключение вида d_Error_Date!nvalid. Чтобы избежать этого, можно использовать статическую булеву функцию is_valid_date, предназначенную для контроля допустимости значений.
К компонентам этой абстракции (год, месяц и день) можно легко получить доступ. Следующая функция выполняет вставку значения даты в поток ostream.
ostream &operator«(ostream &oa, d_Date &d) {
os.width(2); os « d.montht);
os « 1 / ' ;
os.width (2); os « d.dayl);
os « '/'; os « d.year();
return os;
}
В класс d_Date встроено перечисление Weekday, которое идентифицирует день недели, например Monday (понедельник) и Tuesday (вторник). Перечисление Month используется для идентификации месяца в году, например January (январь) и February (февраль). Этот класс поддерживает каноническую C++-форму и набор арифметических операций и операций сравнения. В следующем фрагменте программы вычисляются различные даты.
d_Date day; // текущий день по умолчанию d_Date today(d_Date::current ()); // другие способы
// получения текущего дня, // но менее эффективные
ccut << "Сегодня " << today << endl;
cout << ++day << " завтраХп”;
day = today + d_Interval(1);
ccut << day << ” завтраХп";
day = today;
cout << --day << "вчераХп";
day = today;
day -= 7;
cout << day << " ровно неделю назадХ.п" ;
day = today;
day += d_Interval(7);
cout << day << " ровно через неделюХп";
day = today;
day.next(d_Date::Tuesday);
ccut << day << " следующий вторникХп";
day = today;
day.previous(d_Date::Friday);
cout << day << ” прошлая пятница\п";
int days = today.days_in_month() - today.day();
cout « days « " дней до зарплаты!\n";
Следующая функция используется для вычисления количества дней до Рождества. Если в этом году Рождество уже прошло, функция определяет число дней, оставшихся до наступления Рождества в следующем году.
int
d_Date today;d_Date today;shoppingDaysTilINextChristmas() t
d_Date today;
d_Date today;
// текущая дата по умолчанию
d_Date Christmas(today.year() , d_Date::December, 25);
iff today > Christmas ){
Christmas = d_Date(today.year()+1, d_Date::December, 25);
return christmas.day_of_year() + today.days_in_month() - today.day() + 1; }
return Christmas.day_of_year() - today.day_of_year();
В стандарт ODMG 2.0 добавлена функция, которая вычисляет разность между двумя экземплярами класса d_Date и возвращает экземпляр класса d_Interval. Если использовать эту новую функцию ODMG 2.0, то приведенная выше функция становится намного проще.
int
shoppingDaysTillNextChristmas() {
d_Date today;// текущая дата по умолчанию
d_Date Christmas(today.year(), d_Date::December, 25);
if (today > Christmas ){
Christmas = d_Date(today.year()+1, d_Date::December, 25) ;
}
d_Interval intrvl = Christmas - today;'
return intrvl.days();
)
Чтобы выяснить, находится ли заданная дата в интервале между двумя другими датами, используется функция is_between. Следующая функция определяет, действует ли еще гарантия на продукт, если известны дата покупки и количество месяцев гарантии. В ней используется функция addMonths, позволяющая добавить положительное число месяцев к заданной дате. Эта функция учитывает, что в разных месяцах различное количество дней.
d_Date
addMonths(const d_Date sd, unsigned int months) {
register unsigned int i;
d_Date date(d.year (), d.month(), 1);
iff months >=12 ){// если месяцев больше 12,
i = months/12;// получаем количество целых лет
date = d_Date(d.year() + i, d.month(), 1);
months -= i*12;// получаем остаток месяцев
}
fori i = 0; i < months; i++ ){ date += date.days_in_month();
)
// Если исходная дата = 29 февраля, нужно уточнить день // для невисокосного года
int dayofmonth = date.days_in_month() < d.day() ?
date.days_in_month() : d.dayf);
date += dayofmonth - 1;// устанавливаем действительный день месяца
return date;
}
int
isWarrantylnEffeet(d_Date spurchase, unsigned int months) {
d_Date warrantyDate( addMonths(purchase, months) );
return d_Date::current().is_between (purchase, warrantyDate);
}
Период времени задается начальной и конечной датами. Функция overlaps определяет, не перекрываются ли два заданных периода времени. Предположим, у каждого человека есть односвязиый список структур Trip, которые содержат информацию о неперекрывающихся по времени поездках, которые он запланировал. Этот список упорядочен в соответствии с датами поездок.
struct Trip {.
Trip(const d_Date ssd, const d_Date Srd, struct Trip *n) : startDate(sd), returnDate(rd), next(n) {}
d_DatestartDate;
d DatereturnDate;
// Другие атрибуты
struct Trip * next;
};
Функция newTrip определяет, можно ли запланировать новую поездку. Этой функции передаются даты начала и конца поездки и ссылка на указатель, который ссылается на первый узел в списке (если этот список не пустой). Если время нового путешествия не противоречит другим запланированным поездкам, оно добавляется в список (в порядке сортировки) и функция возвращает ненулевое значение. Если же новая поездка вступает в конфликт с уже существующими, возвращается нуль.
bool
newTrip(d_Date sdate, d_Date edate, struct Trip *strips) {
register struct Trip **tp;
for( tp = strips; *tp; tp = &((*tp)->next) ){
iff overlaps( sdate, edate, (*tp)->startDate, (*tp)->returnDate) ) return false;
iff sdate < (*tp)->startDate ) break;
}
*tp = new Tripfsdate, edate, *tp) ;
return true; )
Класс Mime
Класс d_Time используется для представления конкретного времени суток, отсчет которого поддерживается системными средствами и которое соответствует времени по Гринвичу (Greenwich Mean Time — GMT). Его компонентами являются часы (0—23), минуты (0—59) и секунды (0—59,9999). Все арифметические операции выполняются по модулю 24-часового основания. При попытке инициализировать экземпляр каким- либо недопустимым значением генерируется объект исключения вида d_Error_TimeInvalid.
Нулевой конструктор устанавливает экземпляр в соответствии с текущим временем. Статическая функция current возвращает текущее время. Два следующих объявления переменных инициализируют экземпляр класса d_Time текущим временем.
d_Time timel;
d_Time time2(d_Time::current());
Следующее выражение инициализирует экземпляр временем 3:20 РМ в текущем часовом поясе.
d_Time afternoon_break(15, 20, 0.0f);
Для доступа к компонентам экземпляра класса d Time предусмотрены соответствующие функции. Для вывода значений часов и минут можно использовать следующую функцию (в этом примере не выводим значение секунд).
ostream &
operator« (ostream sos, const d_Time st) {
char fill_char_save = os.fillf);
os.width(2); os.fill (' ' ) ; os « t.hourf); os « ' : ' ;
os.width(2); os.fill (' 0 ' ) ; os « t.minutef); os.fill(fill_char_save);
return os;
}■
Функция fill, определенная для класса ostream, либо возвращает текущий символ заполнения (если не был передан параметр), либо устанавливает символ заполнения класса ostream равным значению, переданному в качестве аргумента.
Встроенное в класс d_Time перечисление (enum) с именем Time_Zone содержит имена для каждого часового пояса. Этот класс по умолчанию поддерживает информацию о часовых поясах и первоначально устанавливает местный часовой пояс. Предусмотрена функция, которая заменяет стандартную установку другим часовым поясом; также существует функция, которая возвращает для часового пояса значение, соответствующее данной местности.
Класс d Time обеспечивает арифметические функции, использующие класс d_Interval. Экземпляр класса d_lnterval можно суммировать с экземпляром класса d_Time и вычитать из него. В результате вычитания одного экземпляра класса d lime из другого экземпляра того же класса получается значение, являющееся экземпляром класса d_Interval.
Для сравнения значений двух экземпляров класса d_Interval можно использовать операторы равенства, неравенства и другие операторы сравнения. Период времени можно задать с помощью двух значений времени. Для определения того, не перекрываются ли два периода времени, можно использовать несколько функций overlaps.
Класс djimestamp
Класс d_Timestamp имеет компоненты даты и времени и предоставляет функции для доступа к ним. Статическая функция current возвращает объект класса d_jrimestamp с текущими значениями даты и времени. Подобно другим классам, связанным со значениями времени, в нем предусмотрены операции сравнения и выполнения арифметических действий. Для определения периода времени можно задать два экземпляра класса d__Timestamp. Чтобы узнать, не перекрываются ли два периода времени, можно использовать несколько функций overlaps. Класс d_Timestamp обеспечивает не просто спаренные экземпляры классов d_Date и d_Time. Экземпляр класса d_Timestamp представляет собой единое интегрированное значение, рассчитанное на выполнение операций обновления. Рассмотрим, например, следующий фрагмент программы.
d_Timestamp ts(1999, 12, 31, 8) ;
d_Interval hr_18(0, 18);
ts += hr_18;
Результирующее значение переменной ts составляет 2 часа дня 1 января 2000 года (January 1, 2000, 2 АМ), как раз время предполагаемого “конца компьютерного света”.
4.4. Ограничения по использованию некоторых типов
Некоторые типы данных языка C++ в определенном смысле проблематичны и не поддерживаются в реализациях ODMG. В частности, следующие типы создают трудности из-за различий в процессорах и компиляторах:
объединения;
битовые поля;
указатели и ссылки.
Поддержка этих типов необязательна для реализаций ODMG, а их использование несовместимо с переносимостью. Рассмотрим некоторые аспекты, которые делают эти типы столь проблематичными.
Объединения
В языке C++ объединение (union) определяет область памяти, которая содержит значение, представляющее одно из нескольких заданных типов данных, причем объединение хранит значение только одного из этих типов.
float_val; amount;smallint;float_val; amount;smallint;union rnulti_type { double
float_val; amount;
smallint;
float_val; amount;
smallint;
d_Ref<Amount> unsigned short } ;
Объем памяти, занимаемой экземпляром типа multi_type, определяется типом, у которого самые большие “притязания” на размер памяти.
Объединение используется в случае, когда существуют определенные опасения насчет расхода памяти и при этом точно известно, что в каждый момент времени программа будет работать только с одним из типов данных, относящихся к определенной группе. Чтобы определить, какие данные хранятся в объединении в текущий момент времени, используется флаг типа (type flag). В следующем объявлении член valueType будет содержать значение, используемое для определения типа переменной value.
value;valueType;value;valueType;class MyClass { multi_type int
value;
valueType;
value;
valueType;
// Другие атрибуты ) ;
Этот флаг (valueType) проверяется перед тем, как программа обратится к содержимому объединения.
void
process_MyClass(MyClass *mp)
switch(mp->valueType){ case 1 :
// используется mp->value.float_val break;
// используется mp->value.amount
break;
:
// используется mp->value.smallint break;
} }
Приложению известно, что флаг valueType определяет тип значения объединения value. Приложение отвечает за установку признака типа при изменении типа значения, хранимого в объединении. Для установки признака типа следует использовать инкапсуляцию.
Типы данных, объявленные в объединении, в разных архитектурах компьютеров могут иметь различные двоичные представления. Для базы данных тип union — это просто набор битов. Когда объект, содержащийся в объединении, заносится в кэш приложения, для некоторых типов данных может потребоваться либо преобразование в другое двоичное представление, либо специальный режим управления кэш-памятью (в случае использования типов d_Ref<Amount> и d_String).
Это является источником проблем для программ ведения баз данных, у которых нет возможности определить тип хранимых в объединении данных. Некоторые реализации предоставляют приложениям средства уведомления базы данных о том, какой тип содержится в объединении. Для каждого объединения в приложении определяется функция распознавания объединения (union discrimination function). Когда объединение помещается в кэш приложения и удаляется из него, база данных вызывает функцию распознавания объединения, чтобы определить, данные какого типа хранятся в этом объединении. База данных может также потребовать, чтобы с каждым из типов объединения было связано некоторое целое число (от 1 до N), которое бы соответствовало порядку объявления типов в объединении. В этом случае в приложении придется объявить соответствующий член целого типа, который бы база данных могла протестировать.
Битовые поля
Двоичное представление битовых полей различно в разных средах компьютеров и компиляторов. В некоторых реализациях баз данных может быть осуществлена поддержка битовых полей с гарантией их корректности в гомогенных средах (при одинаковых компиляторах, операционных системах и процессорах), но вероятность их корректной работы невысока при использовании различных элементов, определяющих среду.
Указатели и ссылки языка C++
Встраивание С++-ссылок в перманентные объекты также проблематично. Например, следующий класс имеет член с именем city, который ссылается на экземпляр класса City.
class Address (
City s city; // пример ссылки C++
// другие атрибуты );
Стандарт ODMG не поддерживает указатели и ссылки языка C++ в качестве атрибутов объекта, хранимого в базе данных. С++-ссылку нельзя хранить в базе данных и применять для доступа к перманентному объекту. Ее можно использовать только для ссылки на транзитные данные. Но поскольку адрес транзитного данного не действителен “за границей" конкретного процесса, от С++-ссылок мало толку при работе с объектными базами данных. Вместо указателей и ссылок в перманентных классах следует использовать класс d_Ref<T>.
4.5. Закяючительнае слово о доменах
Фактически любой класс C++ может служить в качестве типа домена в объектной базе данных, что является одним из преимуществ объектных баз данных перед реляционными базами данных. Но фирмы-производители реляционных баз данных начинают добавлять в свои продукты объектные возможности, и оказывается, что самый простой способ включить объекты в реляционную модель данных — определить новые объектные типы, которые будут служить в качестве доменных типов для столбцов. Более подробно этот метод описан в главе 17.
Доменные типы сами по себе обычно не обладают способностью к перманентности. Экземпляры доменов приобретают качество перманентности после встраивания их в другие классы, которые обладают этой способностью. Сущности информационной модели приложения обычно представляются классами, характеризующимися потенциальной перманентностью. В главе 5 такие классы рассматриваются детальнее.
Глава 5
Перманентные сущности
Сущности проблемной области, которыми управляет приложение, обычно представляются классами. Экземпляры этих классов, как правило, должны быть перманентными и доступными из многих процессов приложения — эта возможность обеспечивается объектными базами данных C++. Из всех категорий типов, существующих в моделях объектных баз данных, именно категория типов сущностей поддерживает перманентность и идентификацию объектов.
В этой главе вы узнаете, как объявить потенциально перманентные классы, и познакомитесь с основными операциями создания, чтения, модификации и удаления экземпляров таких классов. Глава завершается сравнением объектных и реляционных баз данных.
Абстракция сущности
Главнейшая задача, стоящая перед разработчиками на этапе анализа проекта, — выявить в проблемной области все сущности, которые необходимо смоделировать. Для каждой сущности, представляемой в приложении, определяется класс.
Сущность состоит из набора свойств, которые также должны быть определены при анализе проекта. Эти свойства включают атрибуты (некоторого доменного типа), связи (с другими экземплярами) и операции. В классах они представляются данными- и функциями-членами. Все атрибуты данных имеют конкретный тип, именуемый доменом (домены описаны в главе 4).
Экземпляры сущностей часто связаны с другими экземплярами в объектной модели. Эти связи включаются в объявления классов. В классы, участвующие в связях, встраиваются специальные объекты, обеспечиваемые большинством реализаций объектных баз данных. Эти специальные объекты рассматриваются в главе 8.
Операции также связаны с классами сущностей и реализуются как функции- члены. Они должны обеспечивать интерфейс, который олицетворяет абстракцию сущности, а также выполнять модификацию экземпляров. Инкапсуляция реализации локализует семантику и минимизирует объем программного кода, необходимого для понимания реализации классов.
Сущности часто связаны с другими сущностями /S-Л-связями, которые представлены в языке C++ отношениями наследования между классами. Обозначение IS-A происходит от английской фразы is a kind of (это вид), конкретизирующей категорию отношений между двумя предметами. Например, бабочка — это вид насекомого, а автомобиль — это вид транспортного средства. Для представления /5-Д-связей в языке C++ и других объектно-ориентированных языках программирования используется понятие наследования. Объектные базы данных непосредственно поддерживают отношения наследования, объявленные между классами C++. Приложение может использовать для этого как абстрактные, так и конкретные классы.
Как сделать класс перманентным
В объектной базе данных C++ объявления классов приложения служат в качестве определения схемы базы данных. Это одно из фундаментальных преимуществ объектной базы данных.
Базовый класс d_Object
Если приложение использует интерфейс ODMG, то, чтобы экземпляры класса можно было хранить в базе данных, этот класс должен быть выведен (прямо или косвенно) из базового класса d_Object.
В следующем фрагменте программы объявляется класс Person как потенциально перманентный путем выведения его из класса d_object.
class Person : public d_Object { public:
d_stringlast_name;
d_stringfirst_name;
// Другие свойства человека. } ;
Между классами, обладающими способностью к перманентности, также можно определить отношения наследования.
class Employee : public Person { public:
// Свойства служащего. ) ;
Поскольку класс Person выведен из класса d_Object, класс Employee также потенциально перманентен.
Экземпляры
Поскольку класс d_Object обеспечивает поддержку перманентности, многие неверно полагают, что и все экземпляры этого класса автоматически являются перманентными. Экземпляры как раз могут быть или перманентными, или транзитными, хотя транзитные включают ненужные для них данные-члены класса d_Object, требуемые для управления перманентным экземпляром. Именно оператор new, вызываемый для создания экземпляра, определяет, будет этот экземпляр перманентным или транзитным. Три типа оператора new рассматриваются в разделе 5.4.
Каждый перманентный экземпляр имеет идентификатор объекта (object identifier), используемый для, ссылки на данный объект. Этот идентификатор связан с экземпляром до тех пор, пока он не будет удален. Идентификатор объекта отличается “завидным" постоянством: приложение не может его изменить. Изменяться могут
только атрибуты объекта. При этом изменение значения атрибута не влияет на идентификацию объекта, в отличие от реляционной базы данных, в которой идентификаторы хранятся в столбцах таблиц.
Для представления объектной ссылки используется класс d_Ref<T>. Когда экземпляр класса d_Ref<T> хранится в базе данных, он содержит объектный идентификатор адресуемого объекта. Более подробно класс d_Ref<T> рассматривается в главе 6.
Свойства
Свойства класса включают данные- и функции-члены. Свойство имеет имя и тип (домен). Для управления доступом к свойствам класса можно использовать спецификаторы управления доступом public, private и protected. Открытый (public) интерфейс класса должен включать набор операций, которые представляют моделируемую абстракцию.
Атрибуты данных
Для описания характеристик моделируемой сущности используются атрибуты данных. Эти атрибуты не имеют собственных объектных идентификаторов и на них невозможно ссылаться из базы данных.
Объект устанавливает контекст, в котором существуют его атрибуты. Атрибуты обеспечивают значения реализации для поддержки абстрактного значения включающего их объекта. Контекст объекта определяет, какие значения действительны для каждого атрибута. Именно контекст устанавливает многие ограничения, связанные с семантической целостностью.
Атрибуты данных любого объекта следует объявлять с использованием спецификатора private. В слове следует отражены принципы хорошей стратегии проектирования объектов, поскольку реализации не выдвигают такого требования. Данные-члены можно было бы объявить открытыми (public), но это привело бы к потере инкапсуляции. В этом случае операции, определенные для объекта, управляли бы всеми модификациями атрибутов. А ведь именно операции ответственны за поддержку семантической целостности объекта.
Статические данные-члены
В языке C++ статические (static) данные-члены класса имеют только по одной копии, которая совместно используется всеми экземплярами класса. Статический член имеет область видимости класса; он не содержится в каждом экземпляре. Эти члены не хранятся в базе данных, но содержатся в разделе данных каждого процесса приложения, включающего этот класс.
Транзитные члены
В некоторых объектных базах данных разрешается объявлять данные-члены транзитными. Эти данные существуют в объекте, находясь в памяти, но не хранятся в базе данных. Транзитные члены используются в случае, когда некоторые данные напрямую связаны с объектом и необходимы большинству транзакций, но нет никакой надобности хранить их в базе данных. Такие данные могут потребоваться при выполнении вычислений в одной транзакции, но они не имеют смысла в контексте другой транзакции. При этом синтаксис у разных фирм-производителей баз данных различен. Одни обращаются со всеми данными-членами, являющимися указателями, как с транзитными атрибутами. Другие же требуют, чтобы объявление атрибута сопровождалось таким спецификатором, как transient. Стандарт ODMG не поддерживает этого требования.
Предположим, для отображения атрибутов объекта в некотором диалоговом окне используется класс пользовательского интерфейса Form. Объект Form связан с каждым объектом Person. Имея экземпляр класса Person, возможно, будет удобно непосредственно ссылаться на соответствующий объект класса Form.
class Person : public d_Object { // Другие члены класса Person, transient Form *_form; // Ключевое слово transient -
// это дополнительное требование // производителя базы данных, но //не стандарта ODMG или языка C++. } ;
Средство захвата схемы, которое анализирует объявления классов C++, отыщет любые транзитные атрибуты и отметит, что их не нужно запоминать.
Операций
Открытые операции должны обеспечивать интерфейс, который представляет моделируемую абстракцию. Они ответственны за поддержку семантической целостности экземпляра, поскольку им известно, какие изменения допустимы. Эти операции можно рассматривать как “мини-транзакции”, определенные для объекта. И в самом деле, каждая транзакция, которая идентифицируется во время анализа для выполнения действий над экземпляром, должна быть определена как функция-член.
Открытые операции можно также использовать для реализации производного атрибута, т.е. свойства объекта, которое вычисляется в результате сравнения с некоторым хранимым свойством. Значение производного атрибута часто базируется на значениях других атрибутов данных. Термин производный не подразумевает здесь никакого наследования. Например, класс Person может определить операцию аде.
d_UShort
Person::аде() const {
d_Date today = d_Date::current();
d_UShort years = today.year() - _birth_date.year();
if( today.day_of_year() < _birth_date.day_of_year() )
--years; return years;
}
Одни операции могут представлять собой прямые преобразования атрибутов данных, обеспечивая поведение чтения-записи, т.е. предоставляя возможность считывать (geZ) и устанавливать (sez) нужные значения атрибутов. Однако другие операции могут производить вычисления с атрибутами данных и возвращать некоторое значение, по существу, определяя производные атрибуты. Если и хранимые, и производные атрибуты доступны посредством операций, то в доступе к ним не существует никакой семантической разницы. Пользователю класса не нужно знать, какие функции возвращают хранимые атрибуты, а какие — производные. Этот подход используется в функциональной модели данных (functional data model), которая определена в некоторых книгах по базам данных, и обеспечивает существенную гибкость, позволяя изменять реализацию класса без какого бы то ни было влияния на пользователей класса.
Если доступ осуществляется посредством операций, к любой из них можно добавить некоторое действие, выполняемое при каждом запросе доступа к атрибуту, обеспечивая тем самым операцию, которая работает подобно триггеру. Производные атрибуты и триггеры часто поддерживаются в других технологиях баз данных. Для поддержки этих возможностей в объектной базе данных операции, определенные в классе, обеспечивают соответствующий механизм.
Создание
Экземпляры классов создаются путем вызова оператора new. В стандарте ODMG в качестве членов класса d_Object определены три оператора new. Помимо них, реализации могут включать дополнительные операторы, позволяющие воспользоваться преимуществами средств, предоставляемых конкретной фирмой-производителем продукта. Специальный оператор new, используемый для создания экземпляра, определяет, будет ли этот экземпляр транзитным или перманентным. Но после создания перманентный экземпляр нельзя переделать в транзитный и, наоборот, транзитный — в перманентный.
Создание транзитных экземпляров
Существует два способа создания транзитного экземпляра с помощью интерфейса ODMG. Все локальные переменные потенциально перманентных классов являются транзитными. Например, следующая функция объявляет экземпляр класса Person в виде локальной переменной.
void recruit() {
Person candidate;
}
Локальная переменная candidate не будет храниться в базе данных.
Для создания транзитного экземпляра можно использовать две формы оператора new. Предположим, что в приложении был определен класс Employee, выведенный из класса Person. При выполнении следующего фрагмента программы будут созданы два экземпляра класса Employee.
Employee *empl, *emp2; empl = new Employee(); emp2 = new(d_Database::transient_memory, "Employee”) Employee();
Первый оператор new использует стандартный синтаксис, не принимающий дополнительных параметров. В этом случае создается транзитный экземпляр, как и при использовании других операторов new, вызываемых без аргументов в приложении C++. Второй оператор new имеет аргумент, который является указателем на объект класса d Database. Благодаря этому указателю станет ясно, в какой базе данных должен быть размещен новый объект. Эту функцию можно использовать для создания либо транзитных, либо перманентных экземпляров. Открытое (public) статическое данное-член класса d_Database с именем transient_memory как раз и используется для обозначения того, что создаваемый объект должен быть транзитным. Если бы первый аргумент, передаваемый оператору new, был указателем на открытую базу данных, то создаваемому экземпляру была бы уготована перманентная “судьба” — обрести свое пристанище в этой базе данных.
В продуктах некоторых фирм-производителей действуют правила, согласно которым уже при создании экземпляра должен быть известен его тип. К сожалению, эта информация автоматически недоступна внутри оператора new, поэтому ее приходится включать в обращение к оператору new, чтобы все фирмы-производители баз данных могли поддерживать этот стандарт. При этом все производители поддерживают операторы new, которые требуют задания имени класса. Если же вы используете реализацию базы данных, в которой благодаря использованию дополнительных (более удобных) операторов new задание имени класса необязательно, следует иметь в виду, что в этом случае ваша программа не будет переносимой на другие реализации продуктов.
Созданвг перманентных экземпляров
Следующий оператор new можно использовать для создания перманентных экземпляров. В данном случае перманентный экземпляр создается при использовании упомянутого выше класса Employee.
d_Database db;// допустим, db указывает на открытую базу данных Employee *emp3 = new(&db, "Employee") Employee!);
Экземпляр класса d_Database должен ссылаться на открытую базу данных. Приведенный здесь оператор new — это та же функция, которая была использована в предыдущем примере для создания транзитного объекта, адресуемого указателем етр2. Но в данном случае функции new передается указатель на экземпляр класса d Database, ссылающийся на открытую базу данных. Как видите, эта форма оператора new позволяет создавать как перманентные, так и транзитные экземпляры.
Кластеризация экземпляров
Если некоторый набор связанных объектов пользуется “повышенной популярностью”, то его стоит физически сгруппировать в базе данных. Указание на кластеризацию можно предоставить прямо в процессе создания экземпляра. Растеризация (clustering) — это метод оптимизации, используемый приложениями для уменьшения количества дисковых передач и объема кэш-памяти клиента и сервера, требуемой для доступа к объектам. В некоторых архитектурах кластеризация также сокращает затраты на связь клиент/сервер. В реализациях, которые отображают блоки из кэш-памяти базы данных прямо в страницы клиента, кластеризация уменьшает объем требуемой памяти клиента. Если увеличить число объектов, необходимых для приложения, по отношению к числу запрашиваемых страниц, сократится общее количество страниц, требуемое для содержания всех необходимых объектов.
Стандарт ODMG предоставляет интерфейс, позволяющий указать необходимость кластеризации данного экземпляра.
Employee *emp4 = new(emp3, "Employee") Employee!);
Новый объект, адресуемый указателем emp4, будет подвергнут кластеризации в той же базе данных, причем “неподалеку” от объекта (адресуемого указателем етрЗ), который был создан в предыдущем примере.
Задаваемая “близость” различна для разных производителей: она может означать одну и ту же страницу, сегмент, класс или базу данных. Фирмы-производители могут предоставить другие интерфейсы, которые поддерживают специфические возможности кластеризации, но они не будут обладать переносимостью между реализациями ODMG.
В некоторых реализациях кластеризация обеспечивается автоматически. Приложению даже не нужно задавать ее при создании объекта. Автоматическая кластеризация может опираться на определенный класс или связи определенного экземпляра с другими объектами.
Инициализация при создаиии
В объектной базе данных для любого объекта используются две формы инициализации. Во-первых, при создании объекта его атрибуты должны быть инициализированы. В языке C++ для всех объектов, создаваемых в приложении, эту задачу выполняют конструкторы. Во-вторых, инициализация осуществляется, когда процесс приложения впервые получает доступ к объекту базы данных, который был создан ранее другим процессом приложения. Но теперь создание экземпляра объекта может пройс-
ходить в кэш-памяти клиента нового процесса приложения. Это событие получило название активизации (activation) и рассматривается в разделе 5.7.
В объектных базах данных C++ конструкторы выполняют задачу, которая возложена на них в объектном программировании. Для одного класса приложение может определить несколько конструкторов. Атрибуты, для которых не предусмотрены значения инициализации, должны быть инициализированы нулевыми значениями. Чтобы инициализировать экземпляр нулевым значением, рекомендуется определить нулевой конструктор для доменных типов, определяемых пользователем.
Например, наши “подопытные” классы Person и Employee могут иметь следующие конструкторы. (Напомню, что мы добавили член _form, который ссылается на транзитное данное, используемое для отображения объекта в интерфейсе пользователя.)
Person::Person(const char ‘fname, const char ‘Iname, d_Date bd,
const struct Address Saddr, const char *phone)
:_first_name(fname), _last_name(Iname) ,
_birth_date(bd), _address(addr)
(
strncpy (__phone_number, phone, 10);
_phone_number[10] = 0;
_form = new Form();
Person::-Person()delete _form;)Employee::Employee(constPerson::-Person()delete _form;)Employee::Employee(const}
Person::-Person()
delete _form;
)
Employee::Employee(const
Person::-Person()
delete _form;
)
Employee::Employee(const
char *fname, const char *lname,
d_Date bd, const struct Address &addr, const char *hphone, const char ‘ophone, const char * email, d_Float sal)
Person(fname, Iname, bd, addr, hphene), _office_phone(ophone), _email_address(email), _salary(sal)
Удаление
При работе с объектной базой данных приложение не удаляет любой объект явным образом, пока он не будет удален из базы данных. Удаление объекта из кэшпамяти клиента называется дезактивизацией (deactivation) и описывается в следующем разделе. При удалении объекта вызывается деструктор, если таковой был определен. Он выполняет любую “зачистку”, необходимую для поддержки целостности данных, включая такие операции, как удаление вторичных объектов, управляемых удаляемым объектом. Этот процесс называется распространением удалений (delete propagation).
В интерфейсе ODMG доступны два механизма удалений. Объекты можно удалять либо с помощью оператора C++ delete, либо посредством операции delete_object, определенной для класса d_Ref<T>. Объекты класса Employee, адресуемые указателями етрЗ и етр4, можно удалить из базы данных следующим образом.
d_Ref<Employee> empRef (етрЗ) ;
delete етр4;
empRef.delete_object();
, Оператор delete используется в случае, если приложение имеет указатель на объект, который подлежит удалению. Более того, этот указатель должен быть инициализирован адресом объекта в кэш-памяти приложения, и при этом удаляемый объект должен уже находиться в кэше.
Если в распоряжении приложения есть только ссылка (d_Ref<T>) на объект, используется функция-член delete object. В этом случае при вызове функции delete_object объект может быть (а может и не быть) в кэш-памяти. Для выполнения деструктора экземпляр нужно активизировать в кэше.
После выполнения деструктора объект удаляется из кэша приложения. При успешном завершении транзакции объект удаляется из базы данных. В случае ее прерывания объект продолжит свое сушествование в базе данных подобно другим перманентным объектам, удаление которых возложено на деструктор. На транзитные экземпляры действие транзакции обычно не распространяется.
Деструктор в классе d_Object определяется как виртуальная функция, поэтому для удаления экземпляра производного класса можно использовать указатель или ссылку на базовый класс. Например, если экземпляр класса d_Ref<Person> ссылается на экземпляр класса Employee, то вызов функции delete_object повлечет за собой вызов деструктора класса Employee.
Если класс не имеет деструктора и его экземпляр не находится в кэш-памяти, то при удалении объект активизировать необязательно. Подобным образом некоторые реализации осуществляют удаление объекта без вызова деструктора. Этот метод может обеспечить выигрыш в производительности. В некоторых случаях, возможно, как раз и стоит опустить выполнение деструктора и тем самым избежать затрат, связанных с активизацией объекта в кэше приложения. Приложение само должно определить, какие действия необходимы в случае отсутствия объекта в кэш-памяти. Если же существует необходимость в определенных действиях, то такой механизм неприемлем.
При вызове оператора delete или функции delete_object экземпляр удаляется как из кэша приложения, так и кэша базы данных. Но иногда приложению удобно освободить кэш-память путем удаления из него объекта, но совсем не нужно при этом удалять объект из базы данных. Такой подход распространен для приложений с транзакциями, которые выполняются с таким множеством объектов, что они просто не помещаются в кэше одновременно. В этом случае после обработки объекты можно удалить из кэша, чтобы освободить память для других объектов.
Стандарт ODMG не обеспечивает механизм удаления объекта из кэша приложения без удаления его из базы данных, хотя некоторые частные интерфейсы поддерживают такую возможность. Приложение может помечать объекты как отколотые (unpinned), позволяя программе ведения базы данных неявно “выкачать” такой объект из кэша приложения, когда в кэше не хватает доступной памяти. Тема управления кэшпамятью рассматривается в главе 14.
Активизация а дезактивизация
Поскольку к объектам можно получить доступ из базы данных путем транзакции, они заносятся в кэш-память приложения и удаляются из нее. Эти перемещения и называются активизацией (activation) и дезактивизацией (deactivation) соответственно.
Если объект активизирован, его транзитные данные, как правило, нужно инициализировать. Когда объект покидает кэш, не исключено, что придется выполнить его деинициализацию, т.е. при занесении объекта в кэш нужно использовать операцию динамического выделения памяти, а при удалении его из кэша — операцию освобождения памяти. Дезактивизация имеет место при завершении транзакции или временном выводе объекта из кэш-памяти.
Процесс активизации и дезактивизации иллюстрируется на рис. 5.1. Здесь показаны различные процессы приложений в виде отдельных приложений, получающих доступ к одному и тому же объекту во время его жизненного цикла. Течение времени показано в направлении слева направо. Первое приложение создает новый перма-
нентный объект, который по завершении транзакции дезактивизируется и сохраняется в базе данных. Второе приложение считывает объект, вызывая тем самым его активизацию в кэше. После транзакции объект дезактивизируется и записывается в базу данных (если он был модифицирован). Последнее приложение (справа) получает доступ к объекту, вызывая его активизацию, а затем удаляет его. В процессе удаления объект ни дезактивизируется, ни записывается в базу данных — он полностью удаляется из базы данных.
Функции d_activate и d_deactivate
Виртуальные функции deactivate и d_deactivate класса d_Object вызываются программой ведения базы данных. Любые действия, которые необходимо выполнить приложению, следует “поручить” именно этим функциям.
Когда объект активизирован, приложению, возможно, понадобится выполнить другие действия, например добавить в приложение связанные объекты. Это можно было бы выполнить с помощью функции d_deactivate. Как активизация, так и дезактивизация объекта может потребовать обновления графического пользовательского интерфейса, управляемого приложением.
При использовании языка C++ в среде, автоматически не поддерживаемой средствами управления базой данных, действия конструктора и деструктора должны быть четко согласованными, особенно при выделении и освобождении памяти для вторичных структур данных. Конструктор вызывается при создании объекта, а деструктор — при его удалении.
Функции d_activate и d_deactivate играют аналогичную роль, когда перманентный объект помещается в кэш и удаляется из него. Эти функции выполняют любую инициализацию транзитных данных или другие действия, необходимые для приложения. Перманентные данные-члены не нуждаются в инициализации при активизации объекта. Их значения хранятся в базе данных, и они автоматически инициализируются реализацией базы данных. Однако транзитные члены нужно инициализировать. При дезактивизации объекта любая память, выделенная для транзитных данных, должна быть освобождена. В противном случае произойдет утечка памяти. Для эффективного управления памятью объекта очень важно поддерживать симметричность таких операций.
Приложение 1 начинает работу Person::Person Employee::Employee Employee::d_deactivateПриложение 2Employee::d_activateEmployee::d_deactivateПриложение 3Employee::d_activate Employee::-Employee Person::~PersonПриложение 1 начинает работу Person::Person Employee::Employee Employee::d_deactivateПриложение 2Employee::d_activateEmployee::d_deactivateПриложение 3Employee::d_activate Employee::-Employee Person::~PersonПредположим, что объект, отображенный на рис. 5.1, является экземпляром класса Employee, который выведен из класса Person. Допустим также, что для обоих классов определены виртуальные функции d_activate и d_deactivate. Тогда порядок выполнения конструктора, деструктора и функций d_activate и d deactivate должен быть следующим.
Приложение 1 начинает работу Person::Person Employee::Employee Employee::d_deactivate
Приложение 2
Employee::d_activate
Employee::d_deactivate
Приложение 3
Employee::d_activate Employee::-Employee Person::~Person
Приложение 1 начинает работу Person::Person Employee::Employee Employee::d_deactivate
Приложение 2
Employee::d_activate
Employee::d_deactivate
Приложение 3
Employee::d_activate Employee::-Employee Person::~Person
приложение создает экземпляр Employee вызывается при завершении транзакции выполняется доступ к объекту Employee вызывается при завершении транзакции выполняется доступ к объекту Employee объект удаляется
Обратите внимание, что функции d_activate и d deactivate класса Person не вызываются. Программа ведения базы данных просто вызывает виртуальную функцию, которая в свою очередь вызывает виртуальную функцию, определенную на самом глубоком уровне иерархии наследования данного экземпляра. Программа ведения базы данных не вызывает каждую из функций d_activate и d deactivate в иерархии наследования, поскольку это и так делает компилятор для конструкторов и деструкторов. Ввиду значимости той роли, которую играют эти функции в объектном управлении кэш-памятью, весьма рекомендуется, чтобы приложения в своих реализациях функций d_activate и d_deactivate эмулировали подобное поведение конструктора и деструктора. Таким образом, каждая функция d activate производного класса должна сначала вызывать функцию d_activate своего базового класса, а каждая функция d deactivate производного класса должна завершаться обрашением к функции d deactivate своего базового класса.
void
Employee::d_activate() { '
Person::d_activate();
// Выполняем процесс активизации для экземпляра класса Employee
)*
void
Employee::d_deactivate() {
// Выполняем процесс дезактивизации для экземпляра класса Employee Person::d_deactivate() ;
)
В языке C++ конструкторы встроенных объектов вызываются до конструктора объекта, в котором они содержатся. Аналогично их деструкторы вызываются после вызова деструктора включающего объекта. Встроенные объекты не активизируются и не дезактивизируются явным образом программой ведения базы данных; просто включающий их перманентный объект содержит вызовы функций d activate и d_deactivate. Если встроенные объекты нужно активизировать, приложение должно определить функции-члены для встроенных типов и явно вызвать их из функций d activate и d_deactivate включающего объекта. Если некоторый объект добавляется в качестве члена базового класса, эти функции нуждаются в модификации, включающей вызов соответствующей функции.
Функция d_deactivate не вызывается, если для объекта класса d_Transaction вызвана функция checkpoint. После выполнения функции checkpoint информация о состоянии объектов в кэш-памяти приложения передается базе данных, но сами объекты остаются в кэше для дальнейших манипуляций приложением.
Процесс инициализации во время выполнения функций d_activate и d_deactivate различен для разных реализаций продуктов баз данных. Одни фирмы- производители не обладают архитектурами, которые поддерживают этот тип обработки, другие используют метод, отличающийся от принятого в стандарте ODMG. Разработчики должны помнить, что некоторые реализации ODMG (одни из первых) могут не строго соответствовать этому аспекту стандарта ODMG. Важно понимать, как именно используемая реализация базы данных обрабатывает процессы активизации и дезактивизации.
Проблемы реализации активизации
Данные часто встраиваются в объект, используемый компилятором для реализации некоторых языковых механизмов. Эти данные при переходе от одного приложения к другому могут иметь различные значения. Но они всегда должны быть инициализированы до вызова каких бы то ни было процедур приложения (например, функции d_activate).
Чтобы реализовать полиморфизм в языке C++, виртуальные функции используют указатель vtbi, который содержится в каждом экземпляре. При единичном наследовании классов обычно применяется один г/6/-указатель, а при множественном — несколько г/6/-указателей. v/iZ-указатель указывает на таблицу виртуальных функций, связанную с классом данного экземпляра. В реализацию объектной базы данных vtbl- указатель вносит определенную сложность, поскольку адрес таблицы виртуальных функций класса различен для разных приложений. и6/-указатель инициализируется конструкторами при инициализации объекта. Компилятор выполняет эту инициализацию автоматически в программе, которую он генерирует для конструктора. Но при работе с объектными базами данных конструктор, видимый для приложения, вызывается только при первоначальном создании экземпляра, а не во время его активизации. Когда объект активизируется, утбСуказатель необходимо установить равным соответствующему адресу для данного приложения. В одних реализациях v/17-указатель явно приравнивается нужному адресу при активизации экземпляра. В других выполняется специальный конструктор, определенный базой данных, чтобы код инициализации конструктора, генерируемый компилятором C++, установил значение v/ZV-указателя равным соответствующему адресу.
Виртуальный базовый класс преподносит аналогичные проблемы, поскольку многие реализации виртуальных базовых классов встраивают в каждый экземпляр некоторое значение, которое представляет собой либо адрес, либо смещение экземпляра виртуального базового класса внутри объекта. Это значение может варьироваться от компилятора к компилятору C++. Если оно содержит адрес (а не смещение), то этот адрес зависит от адреса включающего объекта в кэш-памяти клиента.
Модификация экземпляров
При активизации объекта в кэш-памяти приложения программа управления базой данных неявно накладывает блокировку (обычно блокировку чтения), позволяющую осуществить разделяемый доступ. Если приложение предполагает модификацию экземпляра, непосредственно перед этим оно должно вызвать функцию-член mark_modified. Эту функцию-член совсем необязательно вызывать при создании объекта или удалении его из базы данных. Стандарт ODMG не определяет, что случится, если приложение все-таки модифицирует объект до вызова функции mark_modified. Однако следует всегда сначала вызывать эту функцию, а затем приступать к модификации объекта. Если приложение не вызовет функцию mark_modif ied, у вас не будет никакой гарантии, что любые изменения, внесенные в базу данных, будут обнаружены и записаны по завершении транзакции.
Цель вызова функции mark_modified — уведомить базу данных о том, что приложение собирается модифицировать объект, однако во многих (но не во всех) реализациях этот вызов используется для индикации того, что должна быть осуществлена блокировка (обычно блокировка записи). Следовательно, вызов функции mark modifled можно рассматривать как явный сигнал заблокировать объект. Некоторые реализации, не отвечающие стандарту ODMG, предоставляют функцию, которая позволяет приложению накладывать специфический тип блокировки на объект.
Многие операционные системы, работающие с виртуальной памятью, позволяют управлять привилегиями доступа чтения-записи к памяти процесса на постраничной или посегментной основе. В некоторых реализациях объектных баз данных эта возможность используется для управления доступом к объектам. При первом доступе к объекту страница, содержащая этот объект, считывается в приложение с доступом, позволяющим только чтение. При попытке записи объекта на странице со стороны приложения возникает прерывание операционной системы (поскольку эта страница защищена операционной системой от записи). Реализация объектной базы данных перехватывает и обрабатывает это прерывание, чтобы попытаться наложить блокировку записи на эту страницу. Если блокировка достигнута, защита доступа к этой странице изменяется таким образом, чтобы приложение могло продолжить выполнение и модифицировать объекты на этой странице. Реализации, использующие этот подход, не требуют от приложений (для наложения нужных блокировок) вызова таких функций, как mark_modif ied. Но для переносимости между реализациями ODMG приложению следует вызвать функцию mark_modified.
Если для класса используется инкапсуляция и все атрибуты данных определены со спецификатором доступа private, то лишь операции этого класса могут модифицировать состояние объекта. Операции, которые не модифицируют объект, следует объявлять константными (с помощью модификатора const), а компилятор затем сам сможет обеспечить данной функции конкретное применение: только для чтения. Операциям, которые модифицируют объект, перед изменением атрибута следует вызвать функцию mark_modif ied. Если последовательно придерживаться такой тактики, то приложению, использующему этот класс, не придется вызывать функцию mark__modified напрямую.
void
Person: ,-newAddress (struct Address &addr); ( mark_modified (); _address = addr;
)
При вызове функции mark_modified реализация базы данных гарантирует, что при успешном завершении транзакции значение экземпляра будет записано в базу данных. Многие реализации баз данных для определения модификации часто поддерживают флаг, называемый битом исправлений (dirty bit). Этот бит может сопровождать не только отдельные экземпляры, но и более мелкие элементы. Программа ведения базы данных отслеживает экземпляры, которые изменяются во время транзакции, а по ее завершении записывает модифицированные объекты в базу данных. Поэтому приложению не нужно распространять обновления на базу данных.
Этот метод значительно снижает стоимость разработки. При использовании других технологий взаимодействия с базами данных приложениям приходится явно передавать состояние всех модифицированных объектов базе данных. При этом приложение должно знать, какие объекты нужно записать в базу данных, а также должно использовать для каждого класса специальные процедуры, записывающие его экземпляры в базу данных. Это может значительно увеличить объем дополнительного программного кода, в написании которого отпала бы необходимость при использовании объектных баз данных.
Управление параллелизмом
Существует две формы управления параллелизмом: пессимистическая и оптимистическая. Разработчики больше знакомы с пессимистической формой управления, которая более популярна. Спецификация ODMG требует поддержки только для пессимистического управления, но некоторые производители могут поддерживать и оптимистическую форму. Какая же разница между ними?
Как пессимистическое, так и оптимистическое управление параллелизмом предусматривают выполнение блокировок и контроль конфликтов блокировок. Но различие состоит в том, когда именно выполняются эти действия во время транзакции. Каждый вид оптимизируется для различных сценариев конфликтов блокировок. Пессимистическое управление подразумевает, что конфликты блокировок весьма вероятны, в то время как оптимистическое предполагает, что они маловероятны. При пессимистическом управлении параллелизмом блокировки накладываются перед доступом к объекту в целях чтения или записи, причем проверка конфликта блокировок выполняется немедленно. А при оптимистическом установка блокировок и контроль их конфликтности выполняется только по завершении транзакции. Ориентируясь на пессимистическое управление, приложение более оперативно узнает о наличии конфликта и поэтому работает лучше всего, когда существует вероятность конфликтов, в то время как при оптимистическом управлении приложение работает эффективнее, когда возникновение конфликта маловероятно. Некоторые производители поддерживают обе формы, обычно с минимальными последствиями для программного кода приложения.
Конфликт блокировок возникает в случае, когда транзакция пытается наложить блокировку, которая несовместима с уже существующей. Блокировки чтения совместимы, но блокировки записи несовместимы ни с блокировками чтения, ни с блокировками записи. При возникновении конфликта блокировок приложение может либо ожидать до тех пор, пока определенная блокировка не будет снята, либо получить немедленный возврат по ошибке. В качестве механизма уведомления в языке C++ часто используются исключения. Для реализаций, которые позволяют ожидать снятия блокировки, приложение может задать длительность ожидания.
Другие операции
С перманентными объектами можно выполнять различные операции. Поведение этих операций зависит от того, какой объект подвергается их воздействию, простой или составной. В случае составного объекта некоторые операции распространяются на подчиненные объекты.
Если объект содержит ссылки на другие объекты, необходимо узнать, к каким объектам применять операции — только к текущему или текущему и адресуемым (посредством ссылок) объектам. Операция, которая используется только для текущего объекта, называется поверхностной (shallow operation). Если же она применяется к адресуемым объектам, именуется глубинной (deep operation). Чаще всего к составным объектам применяются глубинные операции.
Присвапвание
Для перманентного объекта можно определить оператор присваивания. При этом значения атрибутов одного объекта обычно присваиваются атрибутам другого. Объектный идентификатор, связанный с одним объектом, никогда не присваивается другому. Если объект содержит ссылки на другие объекты, должно быть принято решение о присваивании только значения ссылки (поверхностное присваивание) или адресуемого этой ссылкой объекта (глубинное присваивание).
Равенство
Объекты сущностей можно оценивать на предмет их равенства. Обычно они считаются равными, если равны их соответствующие атрибуты. Если объект содержит атрибуты, которые являются ссылками, должно быть указано, что равенство базируется или на равенстве ссылок (поверхностное равенство), или на равенстве адресуемых ссылками объектов (глубинное равенство). Две объектные ссылки могут быть не равны (поскольку они ссылаются на различные экземпляры), несмотря на то что экземпляры, на которые они ссылаются, равны.
Сравнение
Между экземплярами классов некоторых сущностей существует естественное упорядочение, обычно основанное на значениях атрибутов внутри объектов. Операции сравнения гораздо более популярны для типов доменов, чем для типов сущностей. Для упорядочения экземпляров сущности можно определить оператор “меньше” (обычно путем применения оператора “меньше” к типам атрибутов). Зачастую в приложении устанавливается несколько различных упорядочений экземпляров сущности, поскольку набор операторов сравнения, доступный в языке С4-4-, может не отвечать требованиям приложения.
Сравнение с реляционными базами данных
В реляционной базе данных сущность в модели преобразуется в таблицу, которая определяется посредством SQL-оператора create table. Атрибуты сущности представляются в таблице столбцами, а имя и тип каждого столбца определяются в операторе create table. Каждый экземпляр сущности занимает отдельную строку таблицы.
С помощью таблиц невозможно объявить отношения наследования между сущностями. Приложения могут моделировать отношения наследования и управлять ими на верхнем уровне реляционной модели, но базе данных неизвестно об их существовании.
Реляционная база данных не обеспечивает интерфейс для отображения экземпляров классов сущностей между представлением в базе данных и объектной модели С4-4-. Доступ к базе данных (как для чтения, так и записи) выполняется на основе использования столбцов. По возможности обеспечивается преобразование между представлением типа данных столбца базы данных и соответствующим типом данных в языке программирования приложения. При этом именно реализация базы данных диктует, какой должен использоваться тип данных в языке программирования. Преобразование на уровне сущностей должно обеспечить либо приложение, либо объектный слой на вершине реляционного интерфейса.
При работе с объектной базой данных в приложении используется полный экземпляр (со всеми атрибутами), но при этом невозможен доступ лишь к некоторому подмножеству атрибутов. А при использовании реляционных баз данных доступ к подмножеству атрибутов организуется простым и естественным способом.
Реляционная база данных “ничего не знает” об экземплярах в приложении и не поддерживает кэш приложения. Только приложение несет ответственность за то, какие объекты нужно записать в базу данных. Приложение должно явным образом обращаться к базе данных, чтобы предоставить новые значения для модифицированных атрибутов. Должно ли приложение обновлять все атрибуты? Или лучше отследить, какие атрибуты были изменены, и обновить соответствующие столбцы? Нужно ли распространять обновления (сразу при их возникновении) на базу данных или стоит их сначала запомнить, а затем выполнить в процессе завершения транзакции? Разработ-
чик приложения должен принять много решений и позаботиться о создании объемного программного кода для обработки распространения обновлений на базу данных. При использовании объектной базы данных это обрабатывается автоматически, что существенно снижает стоимость разработки.
Реляционные базы данных не поддерживают инкапсуляцию на уровне приложения, поскольку они и “ведать не ведают” об объектной модели приложения. И конечно же, они не предоставляют доступ на уровне объектов. Благодаря языку SQL, ко всем атрибутам данных (столбцам) обеспечивается прямой доступ. Если нужно организовать управление доступом на уровне таблиц, можно определить представление (view), позволяющее ограничить количество доступных столбцов. В этом случае пользователи получают доступ либо к базовой таблице, либо к представлению. Представления также используются для создания производных столбцов посредством SQL- выражений, обеспечивая тем самым некоторое сокрытие информации в реляционной базе данных. Но чаще всего при их использовании существуют ограничения. Например, как правило, невозможно обновить столбец представления.
Используя команду SQL update, можно напрямую изменять столбцы любой строки. Некоторые реляционные базы данных позволяют определить ограничения семантической целостности (которые проверяются по завершении транзакции), чтобы гарантировать, что любые изменения оставляют сущность в логически согласованном состоянии. Приложение сначала вносит изменения, а затем (при завершении транзакции) выполняется контроль их допустимости.
Этот подход отличается от метода, применяемого к объектным базам данных. Приложение определяет явные операции для всех изменений атрибутов и гарантирует их допустимость. Знания, необходимые для внесения допустимых изменений, воплощаются в виде операций, выполняющих эти изменения. Только дизайнер и программист, занимающийся реализацией класса, могут изменять атрибуты напрямую. Такая организация приводит к более высокому уровню сохранения целостности данных.
Главаб
Идентификаци объектов
В объектной базе данных основной метод идентификации объектов состоит в использовании объектного идентификатора (идентификатора объекта). Другой подход предполагает связывание с объектом некоторого имени. Имя состоит из строки ASCII, значение которой определяется приложением. После присвоения имени доступ к объекту в приложении можно осуществлять по имени.
Один или несколько атрибутов класса могут служить в качестве ключа, используемого в некоторых объектных базах данных для локализации объекта. Приложение может уведомить базу данных, что оно “хочет” получить доступ ко всем объектам, значения атрибутов которых удовлетворяют некоторому условию, часто выражаемому предикатом запроса.
Многие объекты моделируют предметы, которые существуют в реальном мире и уже имеют связанные с ними реальные идентификаторы, отличающиеся от объектных. В этом случае можно установить имена объектов или ключи, а затем отобразить реальный идентификатор на объект базы данных.
В этой главе описываются различные методы идентификации объектов, а также уделено внимание сравнению с реляционными базами данных.
Объектный идентификатор
Основное средство идентификации объекта — объектный идентификатор. При создании экземпляра объекта база данных назначает ему уникальный объектный идентификатор. Объектный идентификатор — это не атрибут экземпляра, и его значение не может быть изменено приложением.
Ответственность за генерацию уникальных объектных идентификаторов несет конкретная реализация базы данных. Благодаря своей уникальности, каждый идентификатор соответствует только одному объекту. Однако объектные идентификаторы уникальны только внутри данной области видимости (обычно внутри либо одной базы данных, либо группы баз данных). Стандарт ODMG определяет интерфейс только для доступа внутри одной базы данных. Поэтому объектный идентификатор должен быть уникальным внутри базы данных, хотя в отдельных реализациях баз данных эта область видимости может быть расширена. Среды двух отдельных баз данных могут
иметь частичные совпадения по объектным идентификаторам. Поэтому объекты, которые ссылаются один на другого с помощью объектных идентификаторов, должны существовать в одной и той же области видимости идентификаторов.
В стандарте ODMG для ссылки на экземпляр класса т используется экземпляр класса d_Ref<T>, который должен быть выведен из класса d_Object. Этот экземпляр может также содержать ссылку на экземпляр любого класса, открыто выведенного из класса Т. При сохранении в базе данных экземпляры класса d_Ref<T> содержат объектный идентификатор адресуемого ими объекта. Пока экземпляр класса d_Ref<T> находится в памяти, его значением может быть вполне конкретный объектный идентификатор или же это значение может ссылаться на другое данное в кэш-памяти, которое содержит идентификатор. Приложения не могут получить доступ к реальному представлению объектного идентификатора, причем эти представления в различных реализациях баз данных существенно отличаются одно от другого.
В следующем фрагменте программы показано, как объявить ссылку на экземпляры классов Person и Employee.
d_Ref<Person>person;
d_Ref<Employee>emp;
Класс d_Ref_Any может содержать ссылку на экземпляр любого класса, выведенного из класса d_Object.
d_Ref_Anyaperson;
С экземплярами класса d_Ref_Any можно выполнять не все допустимые для класса d_Ref<T> операции, а только некоторое их подмножество. Для операций, которые требуют задания типа адресуемого экземпляра, экземпляр класса d_Ref_Any необходимо преобразовать в экземпляр класса d_Ref<T>. По завершении преобразования динамически выполняется проверка типов, чтобы убедиться, что данный объект является экземпляром класса Т или класса, производного от класса Т.
Эти классы предоставляют возможность использования интеллектуального указателя (smart pointer), интерфейс которого синтаксически и семантически подобен обычному указателю. Для обеспечения поведения, аналогичного поведению указателя, реализуется перегрузка таких операторов, как операторы разыменования (-> и *). Внутри этих функций базой данных выполняются дополнительные действия. В большинстве существующих реализаций интерфейсов используются классы интеллектуальных указателей, аналогичные классу d_Ref<T>.
Объект ссылки типа d_Ref<T> поддерживает наследование и полиморфизм аналогично указателям языка C++. Предположим, что класс D выведен из базового класса В. Экземпляр класса В может ссылаться на экземпляр любого класса, выведенного из класса В, в том числе и класса D. Причем на экземпляр можно ссылаться безотносительно к его реальному типу. Таким образом, в базе данных вы будете иметь дело с полиморфными ссылками.
class Person : public d_Object {
public:
Person(const char *fn, const char ‘in);
const char *first_name()const;
const char *last_name()const;
// Другие атрибуты.
};
class Employee : public Person {•
/* Определение служащего. */
7777Глава 6. Идентификаций'объектов
77
77
При выполнении следующего фрагмента программы создается экземпляр класса Employee.
d_Database db; // Предположим, что db ссылается // на открытую базу данных.
d_Ref<Person> person = new(&db, "Employee")
Employee("Tina", "Strouble");
Переменная person содержит ссылку на экземпляр класса Employee. Как и при использовании любого указателя, приложение может получить доступ только к атрибутам экземпляра класса Person, но не к атрибутам экземпляра класса Employee. Однако с помощью переменной person могут быть вызваны виртуальные функции и функция, соответствующая классу Employee.
Операция разыменования — одна из самых основных в объектной базе данных. Операторы -> и *, определенные для класса d_Ref<T>, можно использовать для операций вызова функций.
person->last_name();// этот вариант используется чаще
(’person).first_name();
Оператор -> возвращает адрес объекта в кэш-памяти приложения. Функция operator* возвращает С++-ссылку на объект. Если адресуемый объект уже находится в приложении, эти операторы просто возвращают указатель или ссылку на этот объект. Однако при этом может возникнуть необходимость в выполнении некоторых “поисковых работ”. Если объект не находится в памяти, он считывается из базы данных и помешается в кэш приложения.
В некоторых реализациях объектных баз данных разрешено хранить С++- указатели в базе данных и использовать их в качестве ссылок на объекты в этой базе данных.
class Employee : public Person {
Person *spouse; // и другие атрибуты
) ;
При использовании указателей база данных становится более прозрачной, чем при использовании класса d_Ref<T>. В частности, это облегчает перевод существующих структур данных языков С и C++ в среду базы данных, поскольку они уже и так применяют указатели для доступа к транзитным данным. Использование указателей для доступа к базе данных вызвало горячие дебаты среди разработчиков и пользователей объектных баз данных, и лишь немногие фирмы-производители их поддерживают, а стандарт ODMG — нет.
Инициализация л присваивание
Существует несколько способов инициализации экземпляра класса d_Ref<T> или d_Ref_Any. Нулевой конструктор инициализирует ссылку на нулевое значение. Экземпляр класса d_Ref<T> может инициализировать одним из перечисленных ниже способов.
d_Ref<T>
d_Ref<D> для любого класса о, выведенного из класса т
т *
D * для любого класса о, выведенного из класса т
d_Ref_Any'-
Таким образом, допустимы следующие объявления.
d_Ref<Person> person * d_Ref<Employee> Employee * d_Ref_Anyd_Ref<Person> d_Ref<Person> d_Ref<Person> d_Ref<Person> d Ref<Person>d_Ref<Person> person * d_Ref<Employee> Employee * d_Ref_Anyd_Ref<Person> d_Ref<Person> d_Ref<Person> d_Ref<Person> d Ref<Person>person;
d_Ref<Person> person * d_Ref<Employee> Employee * d_Ref_Any
d_Ref<Person> d_Ref<Person> d_Ref<Person> d_Ref<Person> d Ref<Person>
d_Ref<Person> person * d_Ref<Employee> Employee * d_Ref_Any
d_Ref<Person> d_Ref<Person> d_Ref<Person> d_Ref<Person> d Ref<Person>
P = 0;
employee;
e = 0;
any;
pl(person);
p2(p);
p3(employee);
p4(e);
p5(any); // требует проверки типов во время выполнения
Подобным образом экземпляру класса d_Ref<T> можно присвоить экземпляр одного из этих типов.
d_Ref<Person>рб, р7, р8, р9, рЮ;
рб = person;
Р7 = р;
р8 = employee;
р 9 = е ;
рЮ = any; // требует проверки типов во время выполнения
Благодаря отношениям наследования между классами, некоторые инициализации и присваивания недопустимы. Например, следующие строки программы являются запрещенными преобразованиями между ссылками.
d_Ref<Department> dept;
Department *d = 0;
// Класс Department не связан с классом Employee dept = employee;// недопустимо
employee = d;// недопустимо
// Класс Employee выведен из класса Person; с помощью
// ссылки на производный класс нельзя ссылаться на
// объект базового класса (то же касается и указателей). employee = person; // недопустимо employee = р;// недопустимо
При попытке выполнить запрещенную инициализацию или присваивание с использованием ссылки будет сгенерирован объект исключения d_Error_Type!nvalid.
Подобно указателям в языке C++, для некоторых из этих недопустимых инициализаций и присваиваний проверка типов выполняется динамически, т.е. не в период компиляции, а во время выполнения программы. Но у разных реализаций различное отношение к проверке типов. Иногда ограничение шаблонов организовано так, что классы, подобные d_Ref<T>, защищены от доступной для указателей проверки типов (аналогичной во время компиляции), в которой учитывается наследование. Шаблонные функции-члены, не так давно добавленные в C++, позволяют определить шаблонный класс интеллектуальных указателей, в котором разрешается проверка типов таких преобразований во время компиляции. Как только это средство языка станет общедоступным, реализации будут широко использовать его, чтобы позволить проверку типов в момент компиляции.
Нулевые ссылки
Ссылка базы данных может содержать нулевое значение. Классы d_Ref<T> и d_Ref_Any имеют нулевой конструктор, который инициализирует ссылку на нулевое значение. Можно также установить ссылку равной нулю путем инициализации или присваивания ей другой нулевой ссылки или нулевого указателя. С той же целью можно использовать и функцию-член clear.
Существует несколько способов определения того, имеет ли ссылка базы данных нулевое значение. Булева функция is_null возвращает истинное (ненулевое) значение, если ссылка является нулевой. В противном случае она возвращает ложное значение, т.е. 0. И оператор ! возвращает истинное значение при нулевой ссылке.
В следующих примерах иллюстрируются выражения, которые используют эти функции.
d_Ref<Employee> emp;d_Ref<Employee> emp;if( emp.is_null() if( !emp ) . . .i f( emp =- 0 ) .if( emp.is_null() if( !emp ) . . .i f( emp =- 0 ) .// предположим такой вариант
d_Ref<Employee> emp;
d_Ref<Employee> emp;
if( emp.is_null() if( !emp ) . . .
i f( emp =- 0 ) .
if( emp.is_null() if( !emp ) . . .
i f( emp =- 0 ) .
// инициализации
// если ссылка нулевая...
Н если ссылка нулевая...
// ссылка сравнивается с нулевым
Н указателем
Операции разыменования
Операции разыменования используются для доступа к объектам с помощью ссылок. Преобразование ссылки базы данных в указатель называется подстановкой (в оригинале swisdingY Функции-члены operator-> и ptr класса d_Ref<T> выполняют эту подстановку и возвращают указатель на адресуемый экземпляр. Функция operator* возвращает С4-+-ссылку на экземпляр. Эти операторы позволяют классу d_Ref<T> иметь интерфейс, синтаксис и семантика которого очень близки синтаксису и семантике указателя.
void referral(const Person &);
d_Ref<Person>person; // предположим такой вариант инициализации
d_Ref<Employee> emp;// предположим такой вариант инициализации
Person *р;
person->last_name () ;
referral(‘person); // передает C++-ссылку объекта
referral(*emp);// передает С++-ссылку базового класса
р = person.ptr ();// ptr возвращает кэш-адрес объекта
р = emp.ptr();
Чтобы в явном виде получить доступ к адресу объекта в кэш-памяти, необходимо вызвать функцию ptr. Обычно для доступа к члену объекта в приложении используется функция operator-> или operator*, если требуется задание ссылки на объект, как показано в предыдущем фрагменте программы.
Если в приложении нужно убедиться, что экземпляр класса d_Ref<T> ссылается на объект, необходимо сначала проверить этот экземпляр на равенство нулю. При попытке разыменовать нулевой экземпляр класса d_Ref<T> с помощью одной из перечисленных выше функций генерируется исключение d_Error_RefNull.
Перед разыменованием адресуемый объект может находиться (а может, и нет) в кэшпамяти приложения. Если объекта нет в памяти, программа ведения базы данных должна отыскать его в базе данных, передать в кэш и активизировать его. Если объект уже находится в кэш-памяти, он становится совершенно прозрачным для приложения.
Эти функции могут отличаться по длительности промежутка времени, в течение которого возвращаемый ими адрес памяти остается действительным. В стандарте ODMG оговорен минимальный размер этого интервала, но в отдельно взятой реализации адрес может быть допустимым и в течение более длинного промежутка времени. При использовании всех этих функций адрес не будет действительным после транзакции (т.е. после ее завершения или прерывания) или удаления адресуемого объекта.
Если функции operator-> или operator* используются с конкретным экземпляром класса d_Ref<T>, то после его удаления возвращаемый адрес не будет действителен. Следовательно, если в функции определена локальная переменная, то возвра-
щаемый адрес не является действительным после завершения этой функции и разрушения экземпляра класса d_Ref<T>. Этот адрес могут вернуть другие экземпляры класса d_Ref<T>, ссылающиеся на тот же экземпляр. В таком случае адрес действителен до тех пор, пока не разрушатся все ссылки на один и тот же экземпляр. Когда все экземпляры класса d_Ref<T>, ссылающиеся на некоторый экземпляр, будут разрушены, реализация вправе изменить адрес объекта в кэш-памяти даже до завершения транзакции.
Если для получения адреса объекта в кэше вызывается функция ptr, этот адрес будет оставаться действительным до тех пор, пока не завершится транзакция или этот объект не будет удален. Удаление всех экземпляров класса d_Ref<T> для конкретного экземпляра класса т не позволяет реализации изменять адрес объекта в памяти, если функция ptr была вызвана для одного из этих экземпляров класса d_Ref<T>. Таким образом, адрес, возвращаемый функцией ptr, потенциально допустим в течение более длительного времени, чем адрес, возвращаемый функциями operator-> или operator*.
Копирование ссыпки
Конструкторы копий классов d_Ref<T> и d_Ref_Any выполняют операцию поверхностного копирования, подобную операции с использованием указателя С4-4-. Когда экземпляр класса d_Ref<T> встраивается внутрь другого объекта, а включающий объект инициализируется или ему присваивается какое-либо значение, вот тогда и выполняется поверхностное копирование.
class Foo : public d_Object { intx;
d_Ref<Bar>bar;
// Другие атрибуты.
public:
Foofconst d_Ref<Bar> 4);
// Предположим, что конструктор копирования не определен.
) ;
Если приложение не определяет конструктор копии, компилятор автоматически генерирует функцию-конструктор, которая выполняет инициализацию “с сознанием" члена данного класса. Так, при инициализации одного экземпляра класса Foo с помощью другого экземпляра класса Foo оба экземпляра будут содержать ссылки на один и тот же экземпляр класса Ваг.
d_Database db;d_Database db;d_Ref<Bar> d_Ref<Foo> d Ref<Foo>d_Ref<Bar> d_Ref<Foo> d Ref<Foo>// Предположим, что db ссылается H на открытую базу данных.
d_Database db;
d_Database db;
d_Ref<Bar> d_Ref<Foo> d Ref<Foo>
d_Ref<Bar> d_Ref<Foo> d Ref<Foo>
b = new(Sdb, "Ваг") Ваг();
foo = newf&db, "Foo") Foo(b);
newFoo = new(&db, "Foo") Foo(*foo);
Однако, чтобы применить операцию к объекту, адресуемому с помощью некоторого экземпляра класса d_Ref<T>, приложение должно явным образом задать необходимые действия.
Foo:: Foo(const Foo &f)
: x(f.x)
{
bar = newt&db, "Bar") Bar(*f.bar);
// Другая инициализация.
}
С использованием приведенного выше определения конструктора копии класса Foo два его экземпляра будут ссылаться на разные экземпляры класса Ваг. Этот подход называется глубинной инициализацией класса Foo в отношении атрибута экземпляра класса Ваг. Обратите внимание, что экземпляр класса Foo может иметь несколько различных ссылок, и поэтому нужно принимать независимое решение насчет “глубины” копирования (поверхностное или глубинное) для каждой ссылки в отдельности. Такое решение, принятое для инициализации, следует также применить и к операции присваивания.
Равенство
Операторы равенства и неравенства (== и ! =) определены для обоих классов d_Ref<T> и d_Ref_Any. В операции равенства может участвовать любая из перечисленных ниже комбинаций типов.
d_Ref<T>
d_Ref<D> для любого класса D, выведенного из класса Т
Т *
о * для любого класса о, выведенного из класса т
d_Ref_Any.
Оператор равенства, определенный для класса d_Ref<T>, можно использовать для сравнения ссылки базы данных с нулевой ссылкой.
d_Ref<Employee> nullEmp;
if( emp == nullEmp ) . . .
while ( emp != nullEmp )(...}
Здесь выполняется только операция поверхностного равенства, т.е. участвует только идентификатор объекта, а не сам адресуемый объект. Такая операция называется идентификационным равенством (identity equality). Для определения равенства адресуемых объектов их ссылки нужно разыменовать.
Предположим, что операция равенства определена для классов Foo и Ваг, взятых из приведенных выше примеров. Равенство для класса Foo может базироваться на значении переменной bar (обе переменные bar ссылаются на один и тот экземпляр)
// Поверхностное равенство:
int operator==(const Foo &f, const Foo &g) (
return f.x == g.x && f.bar == g.bar;
}
или же равенство может базироваться на значениях, адресуемых переменной bar.
// Глубинное равенство:
int operator==(const Foo &f, const Foo sg) {
return f.x == g.x && *f.bar == *g.bar;
}
Разработчик приложения должен решить, какая операция подходит для стоящей перед ним задачи. Если экземпляр класса Foo представляет собой составной объект, а экземпляр класса Ваг — подобъект, то, скорее всего, приемлемо глубинное равенство.
В языке C++, длд сравнения значений указателей используются операторы сравнения (<, <=, > и >=). С точки зрения семантики, это имеет смысл только тогда, когда адресуемые указателями данные расположены в непрерывной области памяти, как в случае с С++-массивом. Эти операторы не определены для экземпляров класса d_Ref<T>.
Прозрачный gocmgn
Если для доступа к объекту используется объектный идентификатор, можно рассчитывать на несколько форм прозрачного доступа, которые дают приложениям многочисленные преимущества.
Экземпляр класса, выведенного из класса d_Object, может быть транзитным или перманентным. Экземпляры класса d_Ref<T>, хранимые в памяти, используются для ссылки либо на перманентные, либо на транзитные объекты вполне прозрачным образом. Экземпляр класса d_Ref<T>, хранимый в базе данных, может ссылаться только на перманентный объект. Транзитный объект существует лишь во время текущего процесса и доступен только в адресном пространстве этого процесса. Ссылка на транзитный объект не имеет никакого смысла для другого приложения. Если в базу данных записывается перманентный объект, содержащий экземпляр класса d_Ref<T>, который ссылается на транзитный объект, эта ссылка устанавливается равной нулевому значению.
При разыменовании экземпляра класса d_Ref<T> объект активизируется в кэшпамяти приложения, если еще не был активизирован. Приложению не нужно знать, находится этот объект в кэше или нет. При необходимости оно может просто ссылаться на него, а база данных сама позаботится об активизации объектов. Рассмотрим схему, показанную на рис. 6.1. Приложение может вначале получить доступ к объекту С, затем — к объекту А, а после этого — к коллекции объектных ссылок, связанных с объектом А. Далее приложение может обратиться к каждому из адресуемых этими ссылками объектов, включая объекты с условными именами tl и t2. Затем получает доступ к объекту Вик каждому из объектов, адресуемых коллекцией ссылок, связанной с в. Объекты tl и t2 будут уже активизированы в кэше и нет никакой необходимости считывать их из базы данных. Но приложению и знать необязательно, находятся ли объекты tl и t2 в кэш-памяти. Объектная база данных автоматически заботится об управлении кэшем; в результате приложение имеет только по одной копии каждого экземпляра из базы данных.
Рис. 6.1. Работа с “общими" объектами, адресуемыми разными ссылками
Такая организация способствует прозрачной работе базы данных. У приложения “создается впечатление", что вся база данных является частью процесса приложения. Для доступа к объектам не нужно делать никаких видимых обращений к базе данных. Они сокрыты внутри операции разыменования, и, кажется, будто программный код приложения обращается к объектам, резидентно находящимся в памяти.
Практически все реализации баз данных используют архитектуру класса кли- ент/сервер, в которой сервер базы данных и приложения клиентов могут размешаться на одном или нескольких компьютерах. Многие реализации обеспечивают гетерогенность (разнородность) архитектуры кДиент/сервер: компьютеры могут иметь различ-
ные архитектуры процессора и операционной системы. Такие реализации разрешают получать доступ к объектам на компьютере клиента, независимо от архитектуры компьютера, используемого для сервера, в котором и хранятся объекты.
С помощью объектных ссылок некоторые объектные базы данных позволяют ссылаться на экземпляры, хранимые в любой базе данных, входящей в состав группы баз тайных. Такая группа обеспечивает область уникальности для объектных идентифика- торлг Следовательно, объектную ссылку, встроенную в объект базы данных А, можно использовать в качестве ссылки на любой объект либо базы данных А, либо другой базы данных (в или с) в той же группе баз данных. Таким образом, к объектам всех баз данных группы можно получить “прозрачный” доступ, как показано на рис. 6.2. Такая организация позволяет хранить объектную модель либо в одной базе данных, либо в расчлененной на несколько баз данных, не оказывая при этом никакого влияния на представление объектной модели. Программа может опрашивать объекты, не вникая в сложности обработки “межбазовых” ссылок. Эти ссылки автоматически обрабатываются программой ведения базы данных.
Рис. 6.2. Адресуемые объекты в разных базах данных одной и той же группы
Некоторые объектные базы данных позволяют объекту мигрировать из одной базы данных в другую в пределах одной группы баз данных. Но здесь возникает одна важная проблема, связанная с влиянием миграции объекта на объектные ссылки. Если объектный идентификатор включает информацию о физическом расположении объекта, то приходится говорить о соответствующем изменении объектных ссылок; они должны отражать новое местоположение объекта в базе данных. Одни реализации действительно требуют изменения всех текущих ссылок на объект. Другие же позволяют существующим ссылкам прозрачно ссылаться на объект в его новой базе данных без модификации “старых” ссылок.
Представление и реализация
Физическое представление и реализация объектного идентификатора зависят от архитектуры программного обеспечения базы данных и, конечно же, различны для разных реализаций баз данных. Но приложение изолировано от этих различий. Идентификатор обычно имеет несколько логических или физических компонентов, обозначающих местоположение объекта. Выбор представления влияет на реализацию операции подстановки, которая преобразует объектный идентификатор в адрес объекта в кэш-памяти приложения.
Объектный идентификатор может содержать любой из следующих компонентов:
идентификатор текущей базы данных объекта;
идентификатор базы данных, в которой объект был первоначально создан;
идентификатор сегмента;
идентификатор страницы;
смещение объекта на странице.
Любая реализация имеет некоторое подмножество компонентов, определяющих местоположение объекта. Спектр возможных представлений весьма широк: от полностью логического до полностью физического.
Реализация может ускорить разыменование путем увеличения объема информации о физическом расположении объекта в объектной ссылке. Но в этом случае возникают трудности при перемещении объекта, поскольку приходится изменять все ссылки. Если для доступа к одному объекту используется несколько ссылок, их можно обновлять либо все сразу, либо индивидуально. Немедленное обновление всех ссылок может привести к значительным затратам системных ресурсов (все зависит от количества ссылок).
Представление объектной ссылки в памяти часто отличается от формы ее хранения в базе данных. Для обеспечения эффективного доступа к объекту, если тот уже активизирован, данные в экземпляре класса d_Ref<T>, резидентно хранимые в памяти, соответствующим образом оптимизируются. После активизации объекта в кэшпамяти приложения задача реализации — добиться, чтобы скорость доступа к нему по возможности приближалась к скорости прямого доступа с помощью указателя. Другая цель любой реализации — минимизировать размер объектной ссылки в памяти, чтобы максимально приблизить его к размеру указателя. Размер ссылки при хранении на диске не имеет такого большого значения. Главное — добиться такой же производительности и таких же затрат, связанных с хранением, которые привычны приложениям для транзитных представлений модели.
На рис. 6.3 изображены методы преобразования объектного идентификатора в объект, используемые различными фирмами-производителями баз данных. Объектные ссылки показаны слева, а адресуемые объекты — справа. Ссылка в первой строке представляет собой указатель. Все остальные ссылки являются представителями таких объектов интеллектуальных указателей, как экземпляры класса d_Ref<T>. Для представления ссылки в памяти в первом методе используется С++-указатель, в последующих трех — класс объектных ссылок (такой как класс d_Ref<T>). Они отличаются в деталях представления и способе преобразования в адресуемый экземпляр. Приведенные здесь методы не претендуют на полноту или точность отображения конкретных реализаций. Некоторые фирмы-производители поддерживают сразу несколько методов. Однако большинство из них не горят желанием раскрывать детали своих реализаций, считая их частью своей интеллектуальной собственности, а также по вполне понятным соображениям конкурентоспособности. Новички, принимающие решение о выборе технологии, часто путаются в различных методах производителей, каждый из которых считает свою технологию превосходящей все остальные.
Метод 1: указатели
Для ссылки на перманентный объект здесь используется стандартный С++- указатель, поскольку в некоторых реализациях указатели применяются в базе данных для ссылки на объекты. В памяти такая ссылка хранится как стандартный С++- указатель, а на диске — как объектный идентификатор. Для обработки указателя в Памяти используются два различных подхода. Когда объект, содержащий указатель, первоначально активизируется в кэше, адресуемый объект можно либо немедленно активизировать, либо отложить активизацию до тех пор, пока указатель не будет разыменован в явном виде.
1.
т *
т
оо
о
о
d_Ref<T>
Идентификаторобъекта или ID объекта
I т * I
Кэш-таблица
Хеш-таблицаХеш-таблица
Хеш-таблица
Хеш-таблица
d_Ref<T>
Идентификаторобъекта или ID объекта
Рис. 6.3. Методы преобразования ссылок в объекты
В обоих случаях реализация базы данных должна сначала определить, находится ли объект в кэш-памяти приложения. Если да, то указатель можно установить равным адресу объекта в памяти. При немедленной активизации объект нужно сначала поместить в кэш, после чего указатель устанавливается равным адресу, по которому объект активизируется в кэше.
Недостаток немедленной активизации адресуемого объекта состоит в том, что приложение может не получить реальный доступ к этому объекту. Ведь каждый активизируемый объект может содержать ссылки на другие объекты, которые также должны быть активизированы. В этом случае возможен запуск процесса активизации (подобного рекурсии), в результате которого может быть активизировано потенциально большое количество объектов, которые, возможно, совсем и не нужны приложению.
Активизация адресуемого объекта может быть отложена до тех пор, пока указатель не будет разыменован. При активизации объекта, содержащего указатель, объекту, адресуемому указателем, присваивается адрес, который в данный момент не принадлежит адресному пространству процесса. Указатель инициализируется запрещенным в текущий момент адресом. Когда приложение будет выполнять разыменование указателя, операционная система перехватит попытку использования этого недопустимого адреса. Реализация базы данных (а не операционная система) обработает обнаруженное нарушение адресации памяти и расширит виртуальное адресное пространство процесса, чтобы включить в него область памяти, в которой расположен объект. После этого объект можно активизировать в кэше по-выбранному адресу, и приложение сможет получить к нему доступ. Обратите внимание, что это весьма близко к тому, что происходит, когда приложение использует страницы виртуальной памяти, которые
выгружаются на диск. Реализация базы данных предполагает наличие механизма управления виртуальной памятью для процесса и обращается с базой данных как с расширением адресного пространства процесса.
Метод 2: объектоые ссылки
Ссылка включает как идентификатор объекта, так и указатель, содержащий адрес объекта. Установка указателя происходит при первом разыменовании и активизации объекта в кэше. На объект можно ссылаться из разных мест кэш-памяти приложения. При активизации адресуемого объекта одни реализации баз данных инициализируют указатели во всех объектных ссылках, другие же устанавливают адрес только при следующем разыменовании. Слабое место этого метода — потенциально большой размер ссылки.
Метод 3: объектные ссылки
Объектная ссылка содержит указатель на запись таблицы в памяти, в которой хранится информация обо всех объектах, активизированных или адресуемых в кэше. Запись таблицы включает такие компоненты, как идентификатор перманентного объекта, адрес объекта в кэше (если он активизирован), состояние блокировки объекта, и другую информацию. Если кэш приложения имеет несколько ссылок на один и тот же объект, каждая из этих ссылок указывает на одну и ту же запись таблицы. При первоначальной активизации объекта все ссылки автоматически ссылаются на указатель, содержащий адрес объекта в кэше.
В этом методе при разыменовании используется указатель в ссылке, позволяющей обратиться к нужной записи кэш-таблицы. По содержанию этой записи можно определить, активизирован ли объект и имеет ли он адрес. Если он еще не активизирован, его можно считать из базы данных, а его адрес поместить в запись кэш-таблицы. Если объект уже активизирован, запись кэш-таблицы должна содержать адрес объекта в кэше.
Метод 4: объектные ссылки
Объектная ссылка всегда содержит объектный идентификатор, который представляет перманентное местоположение объекта. При разыменовании это значение преобразуется в адрес объекта в кэше. Для выполнения этой операции часто используются механизмы хеш-таблиц. Преобразование идентификатора объекта в адрес кэшпамяти выполняется при каждом разыменовании. Этот метод требует больше затрат, чем другие. Методы 3 и 4 обеспечивают гибкость, которая выражается в изменении адреса объекта в кэше во время выполнения приложения без модификации ссылок. Но приложения должны “понимать” меру допустимости указателей, возвращаемых из этих ссылок.
Присвоение имен объектам
В базе данных объекту присваивается имя, состоящее из строки ASCII, значение которой определяется приложением. Это имя, которое должно быть уникальным внутри базы данных, представляет собой удобный инструмент для идентификации объекта.
Имена присваиваются экземплярам объектов, но далеко не всем и не всегда. Ведь совершенно необязательно всем экземплярам класса иметь имена или каждому классу — именованные экземпляры. Приложение само “решает”, какие объекты следует “наградить” именами. В качестве имени может быть произвольное строковое значение, но алгоритм присвоения имен должен быть продуман так, чтобы он наилучшим образом отвечал потребностям конкретного приложения.
Как правило, лишь немногие объекты в базе данных имеют имена. Если приложение получило доступ к некоторому объекту по имени, оно по мере надобности просматривает его объектные ссылки, чтобы найти другие связанные с ним объекты. Именованный объект часто называют корневым объектом (root object), или объектом точки входа (entry point object), благодаря его роли в задании стартовой позиции для доступа к набору связанных объектов.
Спецификация ODMG обеспечивает в базе данных единое пространство имен. Если некоторое имя уже используется в базе данных, а вы попытаетесь присвоить его другому объекту, будет сгенерировано исключение d_Error_NameNotUnique.
Для связи имени с объектом в классе d_Database предусмотрена функция set object name. Согласно стандарту ODMG-93 (версия 1.2) объект мог иметь только одно имя. Если функция set object name вызывалась для присвоения имени объекту, который уже имел имя, старое имя удалялось и заменялось новым. Но версия 2.0 стандарта ODMG уже позволяет объекту иметь сразу несколько имен, т.е. при каждом обращении к функции set object name объекту добавляется новое имя. Передав в качестве аргумента этой функции нулевое значение (вместо нового имени), можно удалить все имена, связанные с объектом на данный момент. Этот метод непременно следует использовать при удалении самого объекта, поскольку в версии 2.0 стандарта ODMG не предусмотрено автоматическое удаление имен объекта. Можно легко заменить имя объекта, вызвав функцию rename object и передав в качестве параметров старое и новое имена. Эту же функцию можно использовать для удаления лишь одного (уже ненужного) имени объекта. Для этого достаточно передать ей нулевое значение в качестве параметра, предназначенного для приема нового имени.
Язык объектных запросов (Object Query Language — OQL) позволяет для доступа к объекту применять его имя. Чтобы облегчить использование имен в языке OQL, в имя следует включать только алфавитные символы (без встроенных пробелов или операторов), что позволит корректно анализировать имена. Для обработки многословных имен пробел можно с успехом заменить символом подчеркивания (“_”)•
В следующем примере вызывается созданная приложением функция createDBname, которая создает новую строку, преобразуя пробелы и неалфавитные символы в символы подчеркивания. В этом фрагменте программы иллюстрируется присваивание имени экземпляру класса Department.
d_Ref<Department>
createNamedDept (const char “name, const char *fax) { d_Ref<Department> dept;
const char ‘oname = createDBname(name);
dept = new(&db, "Department") Department(name, fax); db.set_object_name(dept, oname); delete oname;
return dept;
}
Класс d_Database содержит функцию lookup_object, предназначенную для преобразования строки ASCII в экземпляр класса d_Ref_Any. Следующий фрагмент программы можно использовать для доступа к экземпляру класса Department с помощью его имени, содержащегося в переменной dname.
const char *oname = createDBname(dname) ;
d_Ref<Department> dept = db.lookupobject(oname); delete oname;
Если В базе данных хранится объект с именем, адресуемым параметром dname, но этот объект не является экземпляром класса Department или производного от него класса, то при инициализации переменной dept будет сгенерировано исключение
d Error Typelnvalid. Другими словами, при инициализации переменной dept значением экземпляра класса d_Ref_Any, возвращаемым функцией lookup object, обязательно выполняется динамическая проверка типов.
Идентификация на основе значений атрибутов
Объекты можно идентифицировать на основе значений их атрибутов, отвечающих условиям, которые обычно выражаются с помощью таких языков, как OQL или SQL.
В приложении может возникнуть потребность в ассоциативном доступе к объектам. При этом для атрибутов задается набор граничных условий, среди которых может быть требование, чтобы значение атрибута в одном объекте совпадало со значением атрибута в другом объекте. Ассоциативный доступ — это основа для идентификации всех объектов, обхода межобъектных связей и выборки по запросу в реляционной базе данных.
Поскольку для определения и обхода связей между объектами используются объектные ссылки, по всей вероятности, ассоциативный доступ будет менее популярен в объектных базах данных. Но некоторые приложения все же испытывают потребность в ассоциативном доступе, поэтому реализации объектных баз данных обеспечивают такую поддержку в той или иной степени. Ассоциативный доступ поддерживается языком запросов OQL стандарта ODMG.
Ключи
Объект может иметь один или несколько атрибутов, значения которых уникально идентифицируют данный экземпляр. Такие атрибуты объекта называются ключевыми, или ключами. Пользователи баз данных часто получают доступ к объектам посредством ключевого значения. В стандарте ODMG пока не предусмотрена поддержка ключевого доступа, но многие фирмы-производители объектных баз данных уже реализовали такую поддержку.
Ключ может быть простым или составным. Простой состоит из одного атрибута, в то время как составной ключ — из нескольких. Ключ также может быть либо первичным, либо вторичным. Первичный ключ всегда уникален для любого экземпляра класса. При использовании вторичного ключа несколько объектов могут иметь атрибуты с равными значениями. Внешний ключ — это атрибут, применяемый для включения значения первичного ключа некоторого объекта. Он аналогичен объектной ссылке.
Индексы
Для эффективного поиска экземпляров на основе значений одного или нескольких атрибутов класса, на эти атрибуты нужно установить индекс. В индексе для каждого экземпляра класса существует некоторый элемент. В одних реализациях поддерживается индекс, накладываемый только на один атрибут, в других разрешены индексы, учитывающие несколько атрибутов. В стандарте ODMG пока вообще не предусмотрена поддержка спецификации индексов.
Индекс обычно предполагает сравнение значений атрибутов доменного типа. Но не исключено, что тип атрибута может определить приложение, а программа ведения базы данных не знает, как сравнивать его экземпляры. В некоторых реализациях разрешается устанавливать индексы только на примитивные типы данных, с которыми программа базы данных уже “умеет” обращаться. Чтобы реализовать механизм работы с индексами, база данных должна вычислять хешированное значение (хеш-значение) или сравнивать значения двух экземпляров. Некоторые реализации предоставляют средство, позволяющее базе данных получать доступ к операторам, определенным для доменного типа, который объявляется в приложении. '
По мере увеличения количества экземпляров в классе, казалось бы, должно увеличиваться и число индексов, связанных с этим классом. Но обычно это не так, поскольку зачастую индекс имеет эффективное представление для обработки нескольких экземпляров с одинаковым значением атрибута.
При модификации значения индексированного атрибута индекс должен обновляться, чтобы соответствующим образом отражать новое значение. Поддержка индексов в связи с изменением значений атрибутов сопряжена с определенными затратами системных ресурсов. Кроме Того, если индексы содержат много элементов, они занимают значительный объем памяти. Между быстродействием поиска и обновления (из- за затрат на поддержку индексов) существует компромиссное соотношение, т.е. изменение одного показателя возможно только за счет другого.
Хешированные индексы
Хеш-индекс эффективен при поиске объектов с атрибутом, равным заданному значению. Он опирается на хеш-функцию, которая генерирует числовые значения, равномерно распределенные по всему диапазону целых чисел. Затем это число используется в качестве индекса элемента хеш-таблицы. Часто несколько значений доменов приводятся к одному и тому же элементу хеш-таблицы, поэтому с этим элементом может быть связано несколько значений доменов. После обнаружения первого элемента хеш-таблицы поиск элементов с таким же хеш-значением нужно продолжить, чтобы отыскать заданное значение домена. Цель определения хеш- функции — получить такое равномерное хеш-распределение для значений домена, которое бы позволило минимизировать количество значений домена на один элемент хеш-таблицы. При использовании такого подхода сокращается время, требуемое для поиска заданного значения домена.
Для любого домена, используемого в хеш-индексе, требуется две функции. Сначала хеш-функция преобразует значение домена в числовое значение, применяемое в качестве хеш-индекса. А затем функция равенства определит, равно ли значение домена, заданное в условии поиска, значению домена в хеш-индексе.
Индексы со структурой Б-дерева
Индекс со структурой Б-дерева (B-tree) оптимален, когда искомые значения лежат в пределах некоторого диапазона или приложение должно выполнять упорядоченный обход элементов в индексе. Быстродействие многих реализаций структур типа Б- дерева приближается к быстродействию, достигаемому при использовании хеш- индексов. Б-деревья также оказываются полезными, когда определение подходящей хеш-функции либо представляет большую сложность, либо попросту невозможно. Одни реализации поддерживают только Б-деревья, другие же — оба варианта (Б- деревья и хеш-индексы).
Класс Employee может иметь атрибут, содержащий уникальный идентификационный номер. Этот атрибут можно было бы использовать в качестве первичного ключа, а для хеш-индекса это был бы наиболее подходящий индекс. Также имело бы смысл определить вторичный ключ (возможно, составной), включающий фамилию и имя служащего. В данном случае стоило бы использовать индекс со структурой Б-дерева.
Ниденсы иерархии классии '
Объект представляет собой экземпляр конкретного класса, который занимает определенное место в иерархии классов и может иметь базовый или производный класс. Индекс можно установить на один или несколько атрибутов класса. Но можно создать экземпляры классов, которые выведены из индексированного класса. Такие экземпляры также считаются экземплярами индексированного класса. Должен ли индекс в
этом случае содержать экземпляры всех классов, выведенных из индексированного класса, или только экземпляры конкретного класса, который индексируется? Это зависит от задач, решаемых приложением. Индекс иерархии классов включает экземпляры всех производных классов.
Б.4. Резюме
Мы рассмотрели несколько методов идентификации объектов в объектной базе данных, включая идентификаторы, имена и ограничения, накладываемые на значения атрибутов. Идентификатор объекта — это самый эффективный инструмент доступа к объекту, а задание ограничений отличается наименьшей эффективностью. Фирмы- производители баз данных приложили немало усилий к оптимизации доступа к объекту посредством идентификатора. Этот метод перехода от одного объекта к другому позволяет приложениям эффективно использовать ссылки на объекты базы данных (экземпляры класса d_Ref<T>). Более того, эти ссылки полиморфны, что считается весьма существенным фактором во многих объектных проектах.
Когда пользователю нужно идентифицировать конкретный объект, либо задается имя, либо указываются условия, которым должны отвечать значения его атрибутов. При этом сущности часто имеют атрибуты, содержащие идентификаторы, которые используются для идентификации экземпляров в реальном (а не в программируемом) мире. Поэтому во время анализа следует определить, какие объекты должны быть идентифицированы непосредственно пользователями. Затем для обеспечения эффективного доступа можно использовать имена объектов и индексы атрибутов. При этом применение одного метода идентификации не исключает использование другого. Более того, можно применять все три метода. Выбор зависит от задач, стоящих перед приложением, и характера доступа со стороны пользователя.
Механизмы идентификации, обеспечиваемые объектными базами данных, представляют собой супермножество механизмов, предлагаемых другими технологиями баз данных. Сетевые базы данных поддерживают связи (links), которые в некоторой степени подобны идентификаторам объектов. Но связи часто не поддерживают распределенный доступ, и их использование носит более процедурный характер. Кроме того, они не поддерживают уровень прозрачности доступа, присущий объектным базам данных.
В реляционных базах данных за основу берется поддержка ассоциативного доступа. Но они не поддерживают ни идентификаторов, ни имен. Строка таблицы в ANSI SQL-92 не имеет идентификаторов, хотя некоторые продукты предоставляют идентификаторы строк, используемые для ссылки на конкретную строку. В качестве первичного ключа для таблицы может служить один или несколько столбцов, а для обеспечения эффективного поиска нужной строки индекс часто устанавливается на первичном ключе.
Приложение часто имеет класс, который совершенно не связан с идентификатором реального мира. В этом случае, работая с реляционной базой данных, вы должны сгенерировать такой идентификатор. При использовании же объектной базы данных каждый объект автоматически получает объектный идентификатор, и, значит, вам не нужно генерировать его.
Составляющие (столбцы) первичного ключа в реляционной базе данных обрабатываются так же, как и другие столбцы таблицы, и их значения могут изменяться в любой момент. Правила и основные принципы построения реляционной базы данных предусматривают постоянство первичного ключа, но в действительности пользователи могут изменить его путем прямого SQL-доступа. Поэтому для предотвращения подобных изменений должны быть реализованы механизмы представлений и обеспечения безопасности. •
Таблицы, используемые для ссылки на строку, могут содержать внешний ключ, который при необходимости объединяется с первичным ключом для установки связи между таблицами. Значение внешнего ключа применяется для поиска строки в нескольких таблицах. Но после объединения нужно использовать конкретную таблицу. Следовательно, реляционные базы данных не обеспечивают прямой поддержки полиморфных ссылок. Поэтому эту функцию приходится эмулировать приложению; в результате страдает эффективность доступа. Это же препятствует и эффективной прозрачной адресации строк при работе с несколькими базами данных. Объектные базы данных обеспечивают гораздо большую гибкость при доступе к объектам.
Глава 7
Коллекции
Коллекция используется для моделирования набора значений определенного типа. Без коллекций трудно обойтись в объектных проектах, поскольку они исполняют сразу несколько “партий” в объектной базе данных. Они являются частью фундамента, на котором строится объектная модель приложения, и используются для представления связей между объектами в базе данных. Кроме того, коллекции применяются для возврата результатов выполнения запроса.
Существует много видов коллекций, и каждый вид обеспечивает собственный набор характеристик. Коллекции отличаются поддержкой значений-дубликатов, упорядочением элементов, доступом к заданным значениям и итерационными возможностями. К наиболее распространенным видам относятся множества, мультимножества (множества с повторяющимися элементами), списки, массивы и словари. Существует много операций, которые поддерживаются всеми коллекциями, хотя и с различной производительностью.
Все коллекции поддерживают операции вставки и удаления элементов. Некоторые обеспечивают операции, позволяющие определить наличие в них элемента с заданным значением. Каждый экземпляр коллекции характеризуется мощностью, или кардинальным числом (cardinality), которое означает количество элементов, содержащихся в данном экземпляре.
Для навигации по элементам коллекции используется итераторный объект (iterator object), или итератор. В зависимости от коллекции, итератор может поддерживать прямой (forward), обратный (backward) и произвольный (random) доступ к элементам. После связи с коллекцией итератор будет либо ссылаться на текущий элемент, либо находиться в состоянии “конец итерации”. Поскольку коллекции и итераторы поддерживают элементы, тип которых определен пользователем, используется такое средство языка C++, как шаблон (template).
В 1992 году, когда консорциум ODMG еще только приступил к стандартизации, язык C++ не имел стандартного набора классов коллекций. Поэтому многие фирмы- производители предоставили собственные библиотеки классов коллекций, но ни одна из них не считалась стандартной. Каждый производитель объектной базы данных имел собственные классы коллекций, оптимизированные с точки зрения перманентности. Группа ODMG определила набор классов коллекций на основе опыта ее членов.
В 1994 году комитет по стандартизации языка C++ принял стандартную библиотеку шаблонов (Standard Template Library — STL). А консорциум ODMG уже выпустил свою спецификацию ODMG-93 в виде версии 1.1. Поэтому стандарт ODMG имел собственный набор коллекций, который отличался от коллекций библиотеки STL. Кроме того, стандарт ODMG должен был поддерживать (помимо C++) языки Smalltalk и Java, и коллекции ODMG предназначались для реализации в трех языках. Библиотека же STL ориентирована только на C++, и это служит препятствием для ее популяризации внутри объектной модели ODMG. Но по мере того как библиотека коллекций STL стала занимать доминирующее положение на рынке C++, многие производители объектных баз данных приступили к ее поддержке.
Интерфейс ODMG C++ постепенно включает поддержку элементов библиотеки STL. В версии 1.2 был усовершенствован класс итераторов ODMG для поддержки интерфейса константных двунаправленных (bidirectional) итераторов библиотеки STL. Кроме того, был усовершенствован базовый класс ODMG-93 d_Collection для поддержки операций begin и end, определенных во всех коллекциях библиотеки STL. После реализации этих усовершенствований алгоритмы STL уже могли работать с коллекциями ODMG. По сути, коллекции ODMG “совместимы по итераторам” с библиотекой STL, что вполне отвечает философии проектирования STL. В версии 2.0 стандарта ODMG реализована поддержка значительного числа коллекций STL. Они перечислены в следующем разделе.
В этой главе рассматриваются интерфейсы коллекций и итераторов ODMG. Особое внимание уделяется именованным коллекциям в интерфейсе ODMG. Описано применение библиотеки STL в стандарте ODMG, а также рассматривается степень влияния различных реализаций объектных баз данных на эффективность использования коллекций. В заключение вы узнаете, как эмулировать коллекции при использовании реляционной базы данных.
Коллекции и итераторы DOME
Классы коллекций ODMG используются для определения объектной модели приложения. Они параметризированы на основе типа содержащихся в них элементов. Каждый класс коллекции выведен из абстрактного базового класса d_Collection<T>. В этом классе определены операции, поддерживаемые всеми типами коллекций.
В табл. 7.1 указаны некоторые характеристики коллекций ODMG. А на рис. 7.1 показаны отношения наследования между коллекциями. Их полное определение приведено в приложении Б. В версию 2.0 стандарта ODMG был добавлен класс d_Dictionary<K, V>. Элементы коллекций d_List<T> и d_Varray<T> упорядочены в соответствии с их относительной позицией, а не значением. В интерфейсе ODMG определен итераторный класс d_iterator<T>, который используется для всех классов коллекций.
Таблица 7.1. Коллекции ODMG
КоллекцияУпорядоченностьДубликаты разрешены
d_Set<T>
d_Dictionary<K, V>
d_Bag<T>✓
d_List<T> ;"V✓
d_Varray<T>'VV
Базовый класс d_Collection<T> выведен из класса d_object, поэтому любая коллекция может быть независимым перманентным объектом в базе данных. Такая коллекция имеет объектный идентификатор, и на нее можно ссылаться в базе данных. Коллекцию можно также встроить в качестве атрибута перманентного класса. Коллекция этого вида играет роль домена, и в этом случае она не имеет объектного идентификатора, а посему и не адресуема напрямую. Приложения могут также иметь транзитные экземпляры коллекций.
Рис. 7.1. Наследование коллекций
Стандарт ODMG 2.0 поддерживает подмножество коллекций библиотеки STL. Эти коллекции выведены из класса d_Object и обладают потенциальной перманентностью. Их имена состоят из ODMG-префикса d_ и собственного STL-имени. Итак, поддерживаются следующие коллекции.
d_set<T>
d_multiset<T> (аналогия множества с повторяющимися элементами)
d_vector<T> (аналогия массива)
d_list<T>
d_map<K, V>
d_multimap<K, V>
Их интерфейсы согласуются со спецификацией библиотеки STL, но не поддерживают шаблонный аргумент распределителя памяти (allocator) STL. Дело в том, что управление кэш-памятью коллекции поддерживается реализацией объектной базы данных, и приложение переопределить его не может. Коллекции стандарта ODMG 2.0 включают интерфейс класса d_Object.
Множества
Шаблонный класс ODMG d_Set<T> представляет множество элементов типа т. Под множеством подразумевается неупорядоченная коллекция элементов, которые не имеют повторяющихся значений. Если множество целых содержит значения (1,2, 5), то после вставки числа 2 это множество по-прежнему будет содержать те же элементы (1, 2, 5). При вставке любого значения реализация множества должна определить, су-
Птава^Коляекциир--95
шествует ли в нем элемент с таким значением. Множество также должно уметь определять, равны ли два значения используемого типа. Следовательно, для конкретного типа элементов множества должен быть определен оператор равенства, который при этом должен быть доступен для данной реализации множества.
Мультимножестна
Стандарт ODMG включает класс d_Bag<T>, который поддерживает абстракцию мультимножества. Мультимножество — это неупорядоченная коллекция элементов, которая позволяет использование повторяющихся значений. Если мультимножество целых содержит значения (1, 2, 5), то после вставки нового числа 2 это мультимножество будет содержать уже четыре элемента (1, 2, 5, 2).
Списки
Для поддержки абстракции списка стандарт ODMG предоставляет класс d_List<T>. Список — это упорядоченная коллекция элементов, которая позволяет наличие повторяющихся значений. Каждый элемент в списке имеет индекс, определяющий его относительную позицию. В списке предусмотрены операция вставки нового элемента в текущую позицию и операция индексирования для доступа к /-му элементу списка.
Массивы
Массив — это еще один тип коллекции, поддерживаемый объектными базами данных. В то время как массивы C++ имеют фиксированную длину, в приложениях часто возникает необходимость в использовании массивов переменной длины. Поэтому для поддержки таких массивов в стандарте ODMG определен класс d_Varray<T>.
В массивах также определена операция индексирования для доступа к z-му элементу, причем массив выполняет эту операцию быстрее, чем список. Список же превосходит массив в скорости вставки и удаления элементов, расположенных на произвольных позициях. Большинство реализаций также поддерживают С++-массивы, но здесь возможны такие ограничения, как разрешение работать только с одномерными массивами или поддержка только ограниченного набора типов элементов.
Словари
В приложениях часто используется еще один вид коллекции — словарь (dictionary), который также называют отображением (тар), или ассоциативным массивом (associative array). Для этого вида коллекции в стандарте ODMG 2.0 используется класс d_Dictionary<K, v>. Словарь выполняет преобразование ключевого значения одного типа в значение другого типа. Поскольку реализации могут разрешить или не разрешить наличие повторяющихся ключевых значений, словари часто опираются на методы хеширования или структуры данных, которые используют преимущества упорядочения ключевых значений. Следовательно, тип ключа зачастую должен обеспечить хеш-функцию или функцию для сравнения значений экземпляров. Некоторые словари поддерживают ключи в определенном порядке, позволяя выполнение упорядоченной итерации. В объектных моделях обычно применяются словари, в которых ключ строкового типа используется для поиска значения другого типа (часто ссылки на объект).
Типы элементов коллекций 0DMG
Элементы коллекции могут иметь любой тип. Обычно поддерживаются доменные типы: примитивные литералы (int, float), структуры и объекты абстрактных типов данных, а также ссылки (d_Ref<T>) на объекты. В некоторых реализациях на типы элементов накладываются ограничения. Например, если задан объект типа т, то предполагается, что тип элементов коллекции должен быть ссылкой (d_Ref<T>) на экземпляр типа т, а не просто экземпляром типа т, т.е. обеспечивается определенный уровень косвенности.
Как правило, коллекции содержат ссылки на объекты. Предположим, что перманентная коллекция содержит элементы типа d_Ref<T>. Во время транзакции она может содержать ссылки как на перманентные, так и транзитные экземпляры класса т. Но сохранить в базе данных можно только ссылки на перманентные экземпляры. Стандарт ODMG не определяет, что происходит с такой коллекцией по завершении транзакции. Поэтому приложения должны сами избегать такой ситуации.
В реализациях ODMG каждый тип т, используемый в качестве типа элементов коллекции, должен поддерживать следующие операции.
class Т { public:
Т() ;
Т(const Т &) ;
~Т() ;
Т &operator=(const Т &);
friend intoperator==(const T &, const T S);
За исключением оператора равенства, эти функции имеют каноническую форму, которую должны поддерживать все типы. Эти функции должны быть эффективными, поскольку они оказывают непосредственное влияние на производительность коллекций, содержащих элементы данного типа.
Многие объектные проекты используют полиморфизм и должны хранить коллекцию объектов, тип которых выведен из общего базового класса В. Этот тип элементов должен быть ссылкой на базовый класс в (d_Ref<B>). Реальные объекты, на которые в коллекции содержатся ссылки, могут быть экземплярами любого класса, выведенными из класса В открытым способом (с помощью спецификатора доступа public).
Общие операции коллекций
Существует ряд операций, которые являются общими для всех видов коллекций. Они заданы в классе ODMG d_Collection<T>. Помимо поддержки манипуляций отдельными элементами коллекции, эти операции выполняют копирование, присваивание и проверку на равенство целых коллекций.
Поддержка элементов коллекции
Абстрактный базовый класс d_Collection<T> обеспечивает функции поддержки элементов коллекций. Функция cardinality определяет количество элементов в коллекции. Функция contains_element позволяет узнать, содержится ли в коллекции заданное значение. Для нормальной работы этой функции необходимо, чтобы тип т элементов коллекции обеспечивал функцию operator==.
Функция insert_element вставляет в коллекцию заданное значение. Если коллекция, представленная шаблонным классом d_Set<T>, уже содержит такое значе-
ние, добавления элемента не происходит. Размер списка (коллекция d_List<T>), как и размер массива (коллекция d_Varray<T>), при добавлении нового элемента увеличивается на 1, а добавляемое значение помещается в новую (последнюю) позицию.
Функция remove_element удаляет один элемент с заданным значением, если таковой существует. Поскольку коллекции d_Bag<T>, d_List<T> и d_Varray<T> разрешают наличие повторяющихся значений, будет удален первый элемент, к которому осуществится доступ при выполнении прямой итерации. При удалении элемента типа т вызывается деструктор класса. Если элемент коллекции имеет тип d_Ref<T>, вызывается только деструктор класса d_Ref<T>, но не деструктор адресуемого экземпляра класса т. При необходимости этот экземпляр класса т должно удалить приложение. Перегруженная функция remove_all удаляет либо все элементы коллекции, либо элементы, равные заданному значению.
При модификации коллекции автоматически вызывается функция mark_modified. Она включает упомянутые выше функции insert_element и ге- move_element. Это действие может создавать впечатление параллелизма, однако на самом деле модифицировать коллекцию может одновременно только одна транзакция.
Копирование и присваивание кпллекций
Каждый класс коллекции поддерживает каноническую форму операторов языка C++. Нулевой конструктор инициализирует коллекцию нулевыми элементами. Деструктор разрушает объект коллекции и для каждого ее элемента вызывает деструктор, если таковой существует для данного типа элементов.
Конструктор копии инициализирует новую коллекцию, используя коллекцию, переданную в качестве аргумента инициализации. При этом из существующей коллекции копируется каждый элемент типа т, и вызывается конструктор копии класса т. После создания копии каждая коллекция будет иметь собственную копию каждого элемента. Но обратите внимание, если в качестве типа элемента коллекции используется класс d_Ref<T>, обе коллекции будут ссылаться на одни и те же экземпляры класса т. В результате итерации двух неупорядоченных коллекций элементы могут располагаться в различном порядке. Но для упорядоченных коллекций порядок элементов сохраняется.
Операции копирования и присваивания коллекций могут оказаться дорогостоящими, особенно в приложениях, работающих с базами данных, в которых эти операции потенциально громоздки. Быстродействие зависит, в основном, от кардинального числа коллекции, а также от присущей данному типу элементов эффективности выполнения операций инициализации, копирования и деструкции (разрушения), которые применяются к каждому элементу.
Равенство кпллекций
В базовом классе d_Collection<T> определены операторы равенства и неравенства (== и ! =). Их семантика зависит от типа сравниваемых коллекций. Элементы сравниваемых коллекций должны иметь одинаковый тип, но при этом сами коллекции могут быть различного типа. Например, можно проверить на равенство коллекции d_List<T>иd_Set<T>.
Коллекции не могут быть равными, если они различаются по мощности (кардинальному числу). В последующем описании предполагается, что соблюдение этого условия обязательно проверяется. Если какая-нибудь упорядоченная коллекция (d_List или d_Array) сравнивается на равенство с неупорядоченной коллекцией (d_Set или d_Bag), обе коллекции обрабатываются как неупорядоченные. Но для упорядоченных коллекций порядок имеет значение.
Два экземпляра Айв коллекции d_Set<T> равны, если они имеют одинаковое кардинальное число и каждый элемент в экземпляре А равен элементу в экземпляре в. Коллекции типов d_Bag<T>, d_List<T> и d_Varray<T> могут иметь повторяющиеся элементы. Если экземпляр в одного из этих классов сравнивается на равенство с экземпляром А класса d_Set<T> и экземпляр в содержит любые повторяющиеся значения элементов, то экземпляр в не равен экземпляру А. Для равенства экземпляров А и в необходимо, чтобы каждый элемент в множестве А был равен элементу в коллекции в.
Два экземпляра Айв коллекции d_Bag<T> равны, если они имеют одинаковое кардинальное число и одинаковое число вхождений каждого уникального значения в экземпляре А. Когда экземпляр А типа d_Bag<T> сравнивается на равенство с экземпляром коллекции В типа d_List<T> или d_Varray<T>, экземпляры А и В должны иметь одинаковое число вхождений каждого уникального значения в экземпляре А. Поскольку коллекция А не упорядочена, порядок элементов в коллекции в не влияет на их равенство.
Экземпляры А и В типа d_List<T> или d_Varray<T> равны только в том случае, если они имеют одно и то же кардинальное число и равные элементы в каждой позиции коллекций айв.
Итерация
Класс ODMG d_Iterator<T> используется для выполнения итераций по всем коллекциям. Он поддерживает каноническую форму, и вы можете инициализировать экземпляр другим итератором, а также выполнять операцию присваивания. Два итератора можно сравнивать на предмет равенства.
После выпуска версии 1.2 стандарта ODMG-93 класс d_Iterator<T> стал поддерживать STL-спецификацию константного двунаправленного итератора. Константный итератор не позволяет выполнять непосредственную модификацию элементов коллекции. Классом d_lterator<T> поддерживаются функции operator** и operator— в их префиксной и постфиксной формах для обеспечения как прямых, так и обратных итераций. Однако коллекции d_Set<T> и d_Bag<T> не поддерживают обратную итерацию, а при попытке ее выполнить генерируется объект исключения d_Error_IteratorNotBackward.
Стандарт ODMG поддерживает несколько стилей итерации. В следующих программных фрагментах функция-член printprojects класса Department определяется несколько раз (для каждого стиля итерации). Класс Department содержит член projects типа d_List<d_Ref<Project>>. Первые два стиля основаны на оригинальной спецификации ODMG-93, а последние два соответствуют версиям 1.2, 2.0 стандарта ODMG-93 и спецификациям STL.
Функция create iterator возвращает итератор, расположенный в позиции первого элемента (если в коллекции есть хоть один элемент). Функция-член advance перемещает итератор в позицию следующего элемента. Функция-член not_done возвращает истинное значение, если итерация не завершена.
void
Department::printProjects(ostream sos)
{'
d_Ref<Project>proj;
d_Iterator<d_Ref<Project> > pi;
for ( pi = projects. create_iterator (); pi.not_done (); pi. advanced ){ proj = pi.get_element0 ;.
., - .os « proj->name « endl;, , , ,.
.
)'......
Функция get_element обращается к текущему элементу итерации.
Функция-член next класса d_lterator<T> разыменовывает итератор, перемещает его к следующему элементу и проверяет, достигнут ли конец итерации, причем все эти действия выполняются в одном вызове. Функция next возвращает через параметр-ссылку значение элемента, который был текущим перед ее вызовом. Если текущий элемент существует, функция возвращает истинное значение, в противном случае — ложное.
void
Department::printProjects(ostream Sos) {
d_Ref<Project>Proj;
d_Iterator<d_Ref<Project> > pi;
pi = projects.create_iterator();
while! pi.next(proj) ){ os << proj->name << endl;
) )
Функцию get_element не следует использовать вместе с функцией next. После возврата из функции next итератор размещается в позиции следующего элемента, если таковой существует. Внутри цикла while параметр (здесь параметр proj), переданный функции next, должен быть использован для ссылки на текущий элемент итерации. Если внутри этого цикла выполняется обращение к функции get_element или operator*, то возвращаемым ими элементом будет элемент, расположенный после элемента, который был возвращен при вызове функции next. Поэтому, когда цикл дойдет до последнего элемента итерации, генерируется исключение d_Error_IteratorExhausted.
В следующей реализации функции-члена printprojects используется STL-стиль итерации. Функция-член begin, которая определена в классе d_Collection<T>, возвращает итератор, расположенный в позиции первого элемента коллекции (если коллекция имеет хотя бы один элемент). Функция-член end возвращает итератор, расположенный за последним элементом коллекции. Итератор, перемещенный за последний элемент, равен итератору, возвращенному функцией end. Функции-члены begin и end были добавлены в класс d_Collection<T>, чтобы обеспечить совместимость с библиотекой STL.
void
Department::printProjects(ostream Sos) (
d_Iterator<d_Ref<Project> >pi;
d_Iterator<d_Ref<Project> >pe;
for( pi = projects.begin(), pe = proj ects . end () ; pi != pe; ++pi){ os « (*pi)->name « endl;
} }
Операция разыменования operator* перегружена в классе d_lterator<T> для получения доступа к текущему элементу итерации.
Наконец, в последнем стилевом варианте итерации используются алгоритмы STL, в частности алгоритм for_each. Здесь итерационный процесс начинается с итератора, расположенного в позиции начального элемента, и продолжается до тех пор, пока не будет достигнут второй итератор, означающий конец итерации. Каждый элемент, встретившийся во время итерации, передается в качестве аргумента функции, которая была определена в вызове алгоритма for_each.
void
printName(d_Ref<Project> proj) {
cout « proj->name « endl;
}
void
Department::printProjects(ostream &os) {
for_each (pro j ects . begin () , projects.end(), printName); }
“Частная жизнь” разных типов коллекций
В коллекциях обычно определяется ряд операций, которые подходят только для данной конкретной абстракции. В этом разделе и рассматриваются некоторые операции, специфичные для каждого класса коллекции ODMG.
Подмножества о супермножества
Множество А является подмножеством другого множества в, если каждый элемент множества А также есть элементом множества В. Множество А может быть подмножеством множества В и при этом быть равным множеству В, т.е. А=в. В этом случае множество В называется супермножеством множества А. Если множество А является подмножеством множества В, но не равно в, то множество А называют собственным подмножеством В, а множество в — собственным супермножеством множества А. Множества Айв равны только в том случае, если каждый элемент в А является элементом в в, а каждый элемент в в — элементом в а.
В классе d_Set<T> предусмотрены функции, позволяюшие “выяснить отношения" между двумя множествами: является ли одно из них (собственным) подмножеством другого, (собственным) супермножеством другого или они равны. Эти функции возвращают истинное или ложное значение булева типа в зависимости от отношений между двумя множествами.
Операции, пиределенные уля множеств
Классы d_Set<T> и d_Bag<T> с помощью соответствующих операторов поддерживают следующие операции:
объединение (+);
пересечение (*);
вычитание (-).
Класс d_Set<T> позволяет применять эти операции к своим экземплярам, а класс d_Bag<T> поддерживает их для своих экземпляров. Если в приложении возникнет необходимость выполнить одну из этих операций с экземплярами классов d_Set<T> и d_Bag<T>, то экземпляр класса d_Set<T> следует сначала привести к типу d_Bag<T>. Ниже приводится формальное определение перечисленных операций.
Объединением множеств Айв является множество, содержащее все значения, которые присутствуют в множестве А или В или в обоих множествах одновременно. Поскольку множество не должно иметь дубликаты, объединение множеств А и В имеет только один элемент для каждого значения в А или В. Если Айв являются мультимножествами, то количество элементов с данным значением в их объединении равно сумме числа элементов с этим значением в множестве а и числа элементов с этим значением в множестве в.
Пересечением множеств Айв является множество, содержащее все значения, которые имеются как в множестве А, так и в. Два множества Айв являются непересекаю- щимися, если у них нет общих (совпадающих) элементов. Если множества Айв представляют собой мультимножества, то количество элементов в их пересечении с данным значением v равно минимальному из чисел, определяющих количество элементов со значением v в А ив. Например, если А и в являются мультимножествами целых чисел и значение 7 встречается дважды в множестве А и трижды в В, то пересечение А и в будет иметь два элемента со значением 7.
Вычитание множеств Айв, которое также называется дополнением множества А относительно множества в (обозначается как А - в), представляет собой множество всех значений в множестве А, которых нет в множестве в. Если множества айв являются мультимножествами, то для каждого значения v в А количество значений v в А - в равно числу значений v в А минус число значений v в в. Если А = {1,2,2,2,3,3} и в = {1,2,2,3,3,3,4}, то А - в = {2}.
Каждая из этих операций над множествами имеет пять соответствующих функций C++. Они отличаются интерфейсом и способом возврата результирующего множества. Одни помещают результат в один из параметров (т.е. в одно из исходных множеств), другие же создают новое множество. Две функции используют оператор, соответствующий операции, выполняемой над множеством, а остальные применяют имя. Подробности определения каждой из этих функций приведены в приложении Б.
Операции, определенные для коллекций d_List<T> и d_Varrag<T>
Классы d_List<T> и d_varray<T> поддерживают индексированный доступ к своим элементам. Начальное значение индекса равно нулю (в соответствии с соглашением, действующим в С и C++). Размер одномерного экземпляра класса d_Varray<T> можно установить на этапе инициализации или путем передачи нового размера функции resize. Текущий размер массива можно получить, обратившись к функции cardinality.
В классах d_List<T> и d_Varray<T> определены функции, предназначенные для считывания, замены и удаления элемента в любой заданной позиции. При выходе индекса за пределы диапазона генерируется объект исключения d_Error_PositionOutOfRange. Функция insert_element, унаследованная от класса d_Collection<T>, помещает новый элемент в конец коллекции.
В коллекции d_List<T> предусмотрены функции для вставки элемента в первой или последней позиции либо перед заданной позицией или после нее в списке. Можно также прочитать или удалить первый или последний элемент списка.
Коллекция d_List<T> также предоставляет ряд функций для объединения элементов двух списков. Их определения отличаются в зависимости от того, размещается ли результат в одном из исходных списков или создается совершенно новый. Функции concat и operator+ создают новый список, который содержит элементы списка левого операнда, за которыми будут расположены элементы списка правого операнда. Функции-члены append и operator+= добавляют элементы списка-операнда в конец списка-объекта. Последние две функции более эффективны, поскольку результат помещается в списке-объекте, используемом для выполнения этой операции, что позволяет избежать необходимости возвращать объект нового списка по значению. Но если в ваши планы не входит изменение исходного списка, используйте функции, возвращающие список по значению. 1 .
Именованные кшекцнн н экстенты
В приложениях часто возникает необходимость получения доступа ко всем экземплярам класса. В данном разделе рассматривается два основных метода, используемых с этой целью.
В версии 1.2 спецификации ODMG-93 зафиксировано требование поддержки именованной коллекции, которая содержит ссылку на каждый экземпляр класса. Если приложению не нужно обращаться к экземплярам классов, без именованной коллекции со ссылками на них можно вполне обойтись. Коллекция может содержать ссылки на все экземпляры или на экземпляры, принадлежащие некоторому подмножеству. Имя коллекции может быть произвольным. Приложение само должно нести ответственность за поддержку именованной коллекции, поскольку каждый раз, когда какой-нибудь экземпляр создается или удаляется, именованные коллекции нужно модифицировать.
Набор всех перманентных экземпляров класса называется экстентом (extent). Многие экземпляры автоматически поддерживают экстенты. Когда транзакция создает или удаляет экземпляры класса, они автоматически помещаются в экстент класса или удаляются из него. Экстент подобен таблице в реляционной базе данных. Поддержка экстентов реализована в версии 2.0 спецификации ODMG.
Именованные коллекции
Поддержку именованной коллекции можно реализовать в конструкторе и деструкторе класса, если на все экземпляры, как показано в следующем примере, имеются ссылки.
Именованную коллекцию Depts можно использовать для хранения ссылок на все экземпляры класса Department. Конструктор класса Department должен вставлять в эту коллекцию очередную ссылку, а деструктор должен удалять ее. Предположим, что в классе Department реализованы следующие действия.
class Department : public d_Object (
public:
static d_Ref<d_Set<d_Ref<Department> > > depts();
Department(const char *n) ;
-Department();
// Другие public-атрибуты.
private:
static const char * const depts_name;
static d_Ref<d_Set<d_Ref<Department> > > _depts;
// Другие private-атрибуты.
Статический член _depts представляет собой ссылку на именованную коллекцию Depts, которая содержит ссылки на все экземпляры класса Department. При первоначальном создании базы данных приложение должно однократно вызвать функцию create_depts для создания именованной коллекции. Затем для доступа к ней приложение вызывает функцию depts.
const char * const Department::depts_name = "Depts";
d_Ref<d_Set<d_Ref<Department> > >_depts;
d_Ref<d_Set<d_Ref<Department> > > Department::depts()
' { '
if( _depts.is_null() )// еще не было обращения
_depts.= db.lookup_object(extent_name);
if ( _depts.is_null() )(// еще база данных не создана
_depts = new(sdb) d_Set<d_Ref<Department> >; db.set_object_name(_depts, depts_name);
}
return _depts;
}
Каждый из конструкторов класса Department вставляет в эту именованную коллекцию ссылку на создаваемый экземпляр, а деструктор удаляет ее из коллекции.
Department::Department(const char *n) : name(n) (
depts()->insert_element(this);
// Другие действия по инициализации. }
Department::-Department() <
depts()->remove_element(this) ;
// Другие действия деструктора. )
В этом способе поддержки именованных коллекций предполагается, что все экземпляры перманентны, а это как раз бывает далеко не всегда. Часто в приложениях состав экземпляров класса может быть неоднородным, т.е. наряду с перманентными могут создаваться и транзитные экземпляры. Но только приложению известно, “кто есть кто” и куда следует вставить данный конкретный экземпляр. В этом случае реализацию поддержки именованных коллекций нельзя вставлять в конструкторы и деструкторы класса.
В приведенном выше примере ссылки на все экземпляры класса Department содержались в одной коллекции. Но следует иметь в виду, что это сугубо “личное дело” конкретного приложения — решать, какие объекты ему сгруппировать в той или иной коллекции; ведь на один и тот же экземпляр может ссылаться столько коллекций, сколько потребуется приложению.
Экстенты
Поддержка экстентов включена в версию 2.0 спецификации ODMG. Экстент для класса Т аналогичен неупорядоченной коллекции d_Set<d_Ref<T>>, которая содержит ссылку на каждый экземпляр класса т. Экземпляр класса d_Extent<T> не является перманентным, он лишь обеспечивает интерфейс для доступа к перманентным экземплярам класса т с помощью итераторов. Используя этот интерфейс, можно выбрать в экстенте экземпляры, которые удовлетворяют условиям запроса, выраженного в инструкциях языка OQL.
Приложению, в котором нужно получить доступ ко всем экземплярам класса Person (и его подкласса), хранимым в базе данных, следует объявить следующую переменную.
d_Database *dbp;// предположим здесь использование'
// ссылки на открытую базу данных d_Extent<Person>Person(dbp);
Затем эта переменная используется для доступа ко всем экземплярам класса Person. Конструктор класса d_Extent имеет второй аргумент типа d_Boolean, который определяет, включать ли экземпляры подклассов. По умолчанию его значение принимается равным d_True. Если в намерения приложения не входит включение экземпляров подклассов, приведенное выше объявление нужно изменить путем явного задания второго аргумента.;■
d_Extent<Person>Person(dbp, d_False);'
Экстент автоматически поддерживается базой данных, поэтому приложению не нужно беспокоиться об операциях вставки или удаления. При вставке или удалении экземпляров база данных соответствующим образом обновляет экстент. Поскольку приложение не манипулирует экстентом напрямую, в этом случае можно говорить о достижении большей степени параллелизма, чем при использовании именованных коллекций.
Часто для каждого класса в базе данных поддерживается свой экстент. Однако к некоторым экземплярам никогда не осуществляется доступ посредством их экстента, поэтому некоторые реализации позволяют приложению “заявлять” о том, что поддерживаются только экстенты подмножества классов. Предотвращение ненужных затрат, связанных с поддержкой экстентов, способствует повышению производительности всей системы.
Классы, как известно, связаны один с другим отношениями наследования. В экстенте объединяются экземпляры “самого" специфического производного класса. Некоторые реализации могут также поместить экземпляр в экстент базового класса. При такой организации к экземплярам всех классов, выведенных из базового класса, можно получить доступ через единый экстент.
Другие операции над коллекциями
Реализации ODMG часто обеспечивают супермножество действий (называемое правилом супермножества), определенных в стандарте. До появления спецификации ODMG каждая фирма-производитель создавала собственные классы коллекций, которые могли включать действия, специфические для продукта конкретного производителя. После выхода в свет стандарта ODMG фирмы-производители, по всей вероятности, будут поддерживать прежние коллекции и, скорее всего, усовершенствуют реализацию классов ODMG путем включения собственных возможностей. В этом разделе рассматриваются средства, которые не являются частью стандарта ODMG, но обычно присутствуют в существующих на рынке реализациях.
Доступ к адресуемым объектам
Часто используется коллекция, содержащая элементы типа d_Ref<T>. При активизации такой коллекции в кэш-памяти приложения обычно активизируются только экземпляры класса d_Ref<T>, а не объекты, на которые они ссылаются. Однако приложениям, как правило, нужно получить доступ именно к экземплярам класса т, адресуемым экземплярами класса d_Ref<T>.
Приложение может опросить коллекцию и последовательно разыменовать каждый ее элемент типа d_Ref<T>. Но это приведет к последовательной во времени активизации объектов и передаче экземпляров класса Т. Такое решение весьма неэффективно, и оно способно вызвать перегрузку коммуникаций клиент/сервер. В некоторых реализациях разрешается активизировать все адресуемые экземпляры класса Т вместе. Такой метод более эффективен, если реализуется доступ к большинству адресуемых объектов, поскольку снижаются затраты ресурсов на передачу объектов и создаются условия для более удачного распределения кэш-памяти.
Замкнутый доступ
Расширением метода, описанного выше (доступ ко всем объектам, адресуемым коллекцией), является поддержка операции замыкания (closure). В этой операции все объекты, адресуемые конкретным экземпляром, активизируются при активизации
этого экземпляра. На рис. 7.2 каждый экземпляр снабжен меткой, которая понадобится для последующего обсуждения. При активизации объекта al, если выполняется операция замыкания, будут также активизированы объекты Ы— Ь7. Реализации стандарта ODMG позволяют разыменовывать заданное количество уровней (активизация Ы—Ь7 соответствует одноуровневому замыканию). При полном замыкании активизируются все объекты, достижимые посредством навигации. Приложения, получающие доступ к составному объекту, часто используют эту возможность для доступа ко всем его подобъектам. При полном замыкании будут активизированы объекты Ы—Ь7 и с1-с4 (рис. 7.2).
Рис. 7.2. Замыкание
Многие реализации, которые поддерживают операцию замыкания, выполняют ее для всех ссылок подряд. Однако было бы полезнее активизировать только некоторое подмножество адресуемых объектов. Например, приложению, возможно, нужно обработать только первую коллекцию, активизируя экземпляры bl—ЬЗ. Поэтому разработан еще один механизм, именуемый запросом пути (path query), в котором можно использовать выражение пути (path expression), позволяющее осуществлять определенные переходы во множестве связей объектов в целях достижения нужных объектов. Более подробно выражения пути рассматриваются в части II.
Величина кардинального числа
Коллекция может содержать всего несколько элементов или может служить в качестве экстента класса, содержащего отдельный элемент для каждого экземпляра класса т. Кардинальные числа коллекций в базе данных могут отличаться на много порядков. При работе с очень большими коллекциями нужно быть предельно осторожным. Иногда коллекция имеет намного больше элементов, чем в состоянии вместить кэш приложения, а затраты системных ресурсов на передачу объектов могут быть чрезмерно высокими..
Интерфейсы коллекций часто создают иллюзию, что все элементы коллекции находятся в кэш-памяти. Но тот факт, что коллекция активизируется в кэше приложения, еще не означает, что все ее элементы также резидентны. Они активизируются только по мере необходимости, причем это может произойти в логических или физических модулях, в зависимости от организации управления кэш-памятью и стратегий передачи, положенных в основу данной реализации.'
Некоторые реализации никогда не создают экземпляр коллекции в кэше клиента. Приложение цросто манипулирует объектом-представителем (proxy object), который
посылает каждую операцию на сервер. Эти реализации несут минимальные расходы, связанные с активизацией и дезактивизацией. Но отдельные операции, совершаемые над объектом-представителем коллекции, требуют больше контактов клиент/сервер, чем в реализациях, которые активизируют коллекции и выполняют операции локальным образом. Между двумя подходами существует компромиссное соотношение стоимостей. Если приложение выполняет много операций и коллекция относительно мала, активизация оказывается более эффективной.
Одни фирмы-производители посчитали, что интерфейс и реализация коллекции, оптимизированные для небольшого числа элементов, должны отличаться от интерфейса и реализации, оптимизированных для очень большого числа элементов. Поэтому они предоставили разные коллекции, оптимизированные для различных кардинальных чисел. Проектировщик схемы приложения должен выбрать соответствующий вариант коллекции на основе данных о ее предварительном размере.
Другие фирмы-производители предоставили единый интерфейс, но различные реализации, зависящие от значения кардинального числа. При этом реализация полностью сокрыта за интерфейсом коллекции. По мере изменения значения кардинального числа коллекция адаптируется к новым условиям и использует соответствующее представление.
Сравнение автономных коллекций с встроенными
Реализации баз данных при активизации коллекции могут поместить все ее элементы в кэш приложения. При большом размере коллекции это может вызвать как “перерасход” пространства кэша, так и временную задержку при передаче объектов.
Предположим, что существует класс С, который содержит очень большой набор ссылок на экземпляры класса т.
class С : public d_Object { d_Set<d_Ref<T> > setOfT;
Во многих реализациях при активизации любого экземпляра класса С также активизируется коллекция и все ее элементы-ссылки на элементы класса т. Размер множества, несомненно, окажет влияние на время, требуемое для активизации экземпляра класса С.
Лишь в некоторых приложениях может возникнуть необходимость получения доступа к встроенной коллекции, а для других такие затраты системных ресурсов совершенно нецелесообразны. В качестве альтернативного варианта проектирования можно рассмотреть включение в класс С только ссылки на коллекцию.
class С : public d_Object (
d_Ref<d_Set<d_Ref<T> > > refSetOfT; };
Этот метод позволяет получить доступ к экземпляру класса с без негативного влияния активизации коллекции на производительность. Приложения, которым нужен доступ к этой коллекции, могут явно разыменовать ссылку refSetOfT. Эта коллекция может быть напрямую связана с экземпляром класса С, особенно если она была с самого начала встроена в класс с. В таком случае при удалении экземпляра класса с приложение должно явно удалить эту коллекцию в деструкторе класса с, иначе она останется в базе данных без всяких на нее ссылок. Объектные базы данных, ориентированные на язык C++, отличаются одна от другой наличием поддержки кол- лекций-“отходов”, а стандарт ODMG пока не требует такой поддержки.
Модификации коллекций
Когда приложение модифицирует объект коллекции с помощью одной из возможных операций, эта операция вызывает функцию mark_modifled. Благодаря этому вызову, реализация базы данных может установить блокировку записи и зафиксировать факт изменения объекта. Приложению не нужно явно вызывать функцию mark_modified для своей коллекции.
Рассмотрим две ситуации. Если объект коллекции автономен, его обращение к функции mark_modified гарантирует, что этот объект будет сохранен по завершении транзакции. Но если коллекция встроена в некоторый включающий объект, последний обязан вызвать функцию mark modified. Стандарт ODMG не требует, чтобы любая реализация базы данных выполняла этот вызов автоматически. Если некоторая коллекция является встроенным членом перманентного экземпляра, функцию mark_modified включающего экземпляра должно вызвать приложение. Если все модификации встроенных коллекций совершаются функциями-членами включающего класса, они могут обращаться к функции mark_modif ied.
Оптимизации коллекций
Объектные базы данных оптимизируются в целях эффективного хранения коллекции и доступа к ним. Во многих случаях элементы коллекций хранятся в непрерывных областях дискового пространства, что позволяет минимизировать дисковые передачи данных и добиться более эффективного использования кэш-памяти. Коллекция и ее элементы в этом случае могут также передаваться между кэшами большими ‘порциями", а не по одному элементу.
Любая упорядоченная коллекция может иметь представление, которое поддерживает ее порядок на диске; благодаря этому отпадает необходимость в упорядочении элементов при их активизации. При использовании больших коллекций такой подход способен существенно снизить затраты системных ресурсов.
Индексы коллекций
Некоторые реализации позволяют поместить в коллекцию индекс, который содержит “точку входа” для каждого элемента, что позволяет приложению находить все элементы по конкретному значению. Обычно индекс связывается с классом и имеет точку входа для каждого его экземпляра. Индекс коллекции потенциально содержит намного меньшее значение кардинального числа, чем индекс класса.
Тип элемента коллекции может быть примитивным доменом либо ссылкой на экземпляр некоторого класса. Для коллекции, которая содержит такие ссылки, как d_Set<d_Ref<Employee>>, индекс обычно устанавливается на атрибутах адресуемого объекта (в данном случае Employee). Например, в приложении может возникнуть необходимость отыскать в коллекции всех служащих, которые проживают в городе New Albany. Индекс коллекции, установленный на атрибуте city (город), позволяет получить весьма эффективный доступ без затраты времени на выполнение линейного поиска.
Если индекс установить на экземплярах, которые ссылаются на коллекцию, обновления этих экземпляров будут выполняться медленнее из-за затрат на поддержку индексов. Кроме того, индексы “съедают” и память.
Курсоры
Приложение, использующее такой механизм запросов, как OQL, обычно принимает коллекцию, содержащую результат запроса. Экземпляр такой коллекции создается в кэш-памяти приложения. Однако запрос может возвратить такое количество значений, что полный их набор может даже не вместиться в кэше приложения. А приложению, может быть, и нужно-то всего лишь опросить и прочитать последовательно все элементы коллекции. В этом случае невозможно или совершенно неэффективно представлять результат в виде реализованной коллекции в кэш-памяти приложения.
Но этому существует альтернатива, которая называется курсором. (Слово курсор (cursor) является аббревиатурой, построенной на основе словосочетания CURrent Set Of Records, означающего текущий набор записей. — Прим, персе.') Курсор, по сути, является итератором: его интерфейс совершенно аналогичен интерфейсу итератора. Курсор опрашивает виртуальную коллекцию, которая не реализуется в кэш-памяти приложения напрямую. Эта коллекция также может не находиться в кэше базы данных, а просто состоять из значений результата запроса.
Курсор часто позволяет приложению считывать группу значений, эффективно ускоряя передачу из сервера базы данных. Приложение может обозначить количество значений, считываемых одновременно. При выборке большего числа значений реализации часто дезактивизируют предыдущий набор значений. Благодаря курсору приложение может опрашивать потенциально огромный набор значений, не занимая слишком большого объема кэш-памяти приложения.
Реализация коллекций в реляционных базах данных
Большинство реляционных баз данных соблюдают правило первой нормальной формы и не позволяют столбцу содержать значение неатомарного типа. Коллекция считается неатомарной и не поддерживается в качестве типа данных столбца.
Представление схемы базы данных
Для представления коллекции в реляционной модели данных используется таблица, которая имеет отдельную строку для каждого элемента. Как минимум, такая таблица должна содержать столбцы для идентификатора коллекции и значения элемента коллекции. Если некоторый элемент состоит из нескольких значений, то для каждого значения требуется отдельный столбец. Объект с атрибутом коллекции должен иметь столбец в своей таблице, содержащей идентификатор коллекции, которая затем соединяется с таблицей, содержащей элементы коллекции. В таблице можно было бы хранить много коллекций, если бы их элементы имели одинаковый тип.
Если коллекция упорядочена, то для установления порядка требуется специальный атрибут. Если при работе с такой коллекцией приходится выполнять много вставок и удалений, то поддержка атрибута, “отвечающего" за порядок, может оказаться весьма громоздкой и неэффективной.
По умолчанию строки, связанные с коллекцией, могут быть разбросаны по многим блокам диска в базе данных, что приводит к увеличению числа дисковых передач и более интенсивному использованию кэщ-памяти в сервере базы данных. Некоторые реляционные базы данных позволяют определить кластер, объединяющий все строки в таблице с одинаковым значением некоторого столбца. Установка кластера в таблице коллекции на основе идентификатора коллекции позволяет ее элементам “держаться кучкой”, т.е. в одном месте базы данных.
Индексы
Когда коллекция представлена в таблице, индекс обычно устанавливается на столбце, содержащем идентификатор коллекции. Такая организация позволяет получить эффективный доступ к элементам коллекции. Но поддержка индекса — дорогостоящее “удовольствие” в смысле расхода памяти, а также затрат, связанных с обновлением при внесении изменений в индексированный столбец на уровне строки.
Приложению, возможно, понадобится проиндексировать значение элемента. Если моделируется множество, не имеющее дубликатов, то уникальный индекс, установленный как на идентификаторе коллекции, так и значении элемента, предотвратит вставку повторяющихся значений. Он также позволит базе данных эффективно определить, содержится ли заданное значение в данном множестве.
Представление приложения
Реляционная база данных “ничего не знает” о коллекциях в объектной модели приложения. Каждый элемент располагается в отдельной строке. Если приложению нужно создать экземпляр коллекции, в представление этой коллекции в памяти должна быть вставлена строка каждого элемента. Подобные вставки влекут за собой дополнительные затраты системных ресурсов, которые отсутствуют в большинстве объектных баз данных.
Считывание упорядоченной коллекции из реляционной базы данных требует учета еще одной статьи накладных расходов. Упорядочение должно устанавливаться для находящейся в памяти коллекции с участием каждого приложения, которое получает к ней доступ. Чтобы доставка строк к приложению выполнялась в порядке, соответствующем результату сортировки, для обращения к коллекции в SQL-запросе можно использовать директиву order by. Само приложение также могло бы выполнять упорядочение при создании экземпляра коллекции и его заполнении. В любом случае вам не обойтись без затрат на сортировку, которых можно избежать, используя объектную базу данных.
Распространение обновлений объекта коллекции в направлении к базе данных — весьма трудоемкий процесс. Приложение могло бы изменять представление базы данных при каждой модификации объекта коллекции, находящегося в памяти. В качестве альтернативного варианта сразу все изменения можно было бы выполнить по завершении транзакции. Выходит так, что либо приложение должно поддерживать список всех внесенных изменений, либо существующая коллекция базы данных должна быть удалена и воссоздана снова. Однако перечисленные методы вряд ли можно назвать простыми или эффективными.
Резюме
Коллекция представляет собой фундаментальную конструкцию моделирования, применяемую многими приложениями. При использовании объектных баз данных в среде C++ они также являются компонентами определения схемы базы данных. Следовательно, конкретная реализация может оптимизировать свое представление в базе данных. Определение такой составной части семантики коллекции, как упорядочение, способствует обеспечению такого представления, которое эффективно в памяти, на диске и при передачах информации. Непосредственное статическое представление коллекции "содействует существенному повышению производительности. Поэтому при поддержке коллекций объектные базы данных имеют неоспоримое преимущество в производительности перед реляционными базами данных.
Реляционные базы данных обеспечивают прекрасную поддержку динамического отбора множества строк на основе условий, произвольно накладываемых на значения столбцов. Это позволяет определять новые множества строк, о потенциальном существовании которых вы не предполагали во время разработки и которые можно задать во время определения схемы базы данных. Реляционные базы данных обеспечивают высокую степень гибкости благодаря их динамической спецификации множеств. Но за эту гибкость приходится расплачиваться высокой ценой затрачиваемых системных ресурсов.
Ответ на вопрос, что больше подходит вашему приложению — объектная или реляционная база данных, может зависеть от того, должны ли коллекции определяться динамически или их можно определить статически уже при проектировании схемы базы данных. Большинство объектных моделей приложений имеют статические, простые для понимания коллекции. Такие модели могут оказаться выигрышными в производительности с использованием именно объектной базы данных. Другие же приложения требуют гораздо более динамичную спецификацию групп данных. И тогда может быть сделан вывод о том, что реляционная база данных больше отвечает требованиям, предъявляемым к данной конкретной базе данных.
Глава 8
Связи
Соединения между объектами формируются на основе связей (relationships), которые можно смоделировать непосредственно в объектной базе данных. Различают несколько форм связей, причем некоторые из них автоматически поддерживают ссылочную целостность. О существовании ссылочной целостности в базе данных можно говорить только в том случае, если каждая объектная ссылка указывает на действительный объект, т.е. нет “повисших” ссылок на уже удаленные объекты.
В этой главе связи рассматриваются более подробно. Сначала мы остановимся на терминологии, а затем обсудим однонаправленные и двунаправленные связи вместе с интерфейсом связей, поддерживаемым в ODMG. На примере различных сценариев здесь иллюстрируются возможные модели поведения связей, обеспечиваемые реализацией базы данных при удалении или изменении некоторых связей между объектами.
Определение и назначение связей
Связи, как правило, впервые идентифицируются во время определения ER-модели или объектной модели приложения. Для многих приложений обход и поддержка связей занимают значительную часть всего программного продукта.
Связь в объектной базе данных представляется объектом с некоторым набором операций. Установка, модификация и удаление связей между экземплярами реализуется как раз посредством выполнения операций над связанными объектами. Именно операции поддерживают целостность ссылок на обоих концах связи.
Стороны связи
В методологиях построения моделей объектов и данных для представления связей принято использовать форму UML-записи (Unified Modeling Language — унифицированный язык моделирования). Класс в UML-модели изображается прямоугольником. Связь между двумя классами представляется линией, которая соединяет эти прямоугольники (рис. 8.1). Классы в связи часто называют концами (end), или сторонами (side). Для перехода й доступа к экземплярам на другом конце связи в каждом классе используется специальный член (данное или функция), который называется путем перехода (traversal path).
Department
1 ''dept
*employees
Employee
Рис. 8.1. Форма представления связи
Степень связи
Количество классов в связи представляет ее степень (degree). Самое распространенное значение степени равно числу 2; такая связь называется бинарной. В объектных базах данных обеспечивается непосредственная поддержка бинарных связей. Связь со степенью 3 называется тернарной. Связями со степенью 3 и выше должны управлять приложения. Для представления самой связи часто используется отдельный класс, а каждый класс, участвующий в связи, имеет бинарную связь с классом связи.
Тернарная связь иллюстрируется на следующем примере (рис. 8.2). В некотором универмаге ведется регистрация свадебных подарков, и в базе данных фиксируется, кто приобрел подарки для молодоженов. Для каждого лица (то ли дарителя, то ли получателя) в базе данных существует экземпляр класса Person. Класс MarryingCouple содержит ссылки на жениха и невесту, а также на подарки, которые они получили. Каждое лицо, вручающее подарок, имеет множество подарков, возможно, для нескольких пар, вступающих в брак. Класс Gift представляет тернарную связь между парой, принимающей подарок, лицом-дарителем и продуктом, выбранным в качестве подарка.
Рис. 8.2. Пример тернарной связи между классами Person, MarryingCouple и Product
Кардинальное число
Каждая связь имеет кардинальное число, которое представляет собой количество возможных экземпляров на каждом конце связи. Возможными кардинальными числами являются один-к-одному, один-ко-многим и многие-ко-многим. Объектные модели обычно имеют смешанный состав всех трех чисел. Связи типа один-ко-многим часто называют родительско-дочерними. Родительский объект может иметь нуль, один или много дочерних объектов. Кардинальное число для множества объектов в UML- записи обозначается символом “звездочка” (*).
Чтобы ограничить количество объектов на данном конце связи, задается ограничение кардинального числа. Если заранее известны условия работы связей между объектами, задаются как минимальное, так и максимальное значения кардинального числа. Это могут быть вполне конкретные числа, например у велосипеда всегда два колеса, у автомобиля — четыре. Точно так же, как в нашем примере, только два человека могут вступать в брак. Ограничение может “потребовать”, чтобы на одном конце связи было нулевое количество объектов или только один объект. Проектные решения многих приложений часто опираются на заранее известные кардинальные числа связей в объектной модели.
Ограничения кардинальных чисел обычно задаются на этапе анализа объектной модели приложения. При использовании объектной базы данных приложение может указать в ее схеме для некоторого конца связи максимальное значение кардинального числа (“один” или “много”), но пока не реализована возможность задания минимума или максимума в виде конкретного числа (вместо понятия “много”). Эти ограничения должно реализовать приложение, возможно, посредством вызова функции cardinality, чтобы при добавлении нового экземпляра к связи убедиться, что заявленный максимум еще не достигнут.
Однонаправленные и двунаправленные связи
Связи могут быть либо однонаправленными, либо двунаправленными. Предположим, что существует однонаправленная связь между классами Айв. Класс А ссылается на класс в, но в не ссылается на А. Такую связь можно обойти только с конца класса А. Двунаправленные связи можно определить как взаимно обратные, т.е. каждый конец указывает на другой. Если существует двунаправленная связь, то класс а имеет ссылку на класс в, а класс в — на класс А. Обход такой связи можно совершить с любого конца.
Если существует двунаправленная бинарная связь между классами А и в, то с точки зрения экземпляра А путь перехода в классе в, который ссылается на класс А, называется обратной связью (inverse relationship), несмотря на то что существует только одна связь. Ссылка в классе в называется обратной ссылкой (back reference); она используется для указания “в обратную сторону”, т.е. на экземпляр класса А. Член в классе в, используемый для перехода в А, называется обратным путем перехода (inverse traversal path). Эта терминология также применима, если смотреть на данную связь с точки зрения экземпляра в.
Рассмотрим представление древовидной структуры. Для представления любого узла дерева служит единственный класс TreeNode<T>. (Следует иметь в виду, что не все реализации поддерживают пользовательские шаблонные классы.)
template cclass Т>
class TreeNode : public d_Object {
Tvalue;
d_Set<d_Ref<TreeNode<T> > > childNodes;
};
Каждый узел дерева имеет множество ссылок на дочерние узлы дерева. Это иллюстрирует однонаправленную связь. Любой узел ссылается на свой дочерний, но не на родительский. Отнюдь не всегда возникает необходимость в ссылке на родительский узел. Если, например, обходы узлов дерева всегда начинаются с корня, то для отслеживания родительских узлов в приложении можно использовать рекурсию. .
Добавив ссылку на родительский узел, можно сделать описанную выше связь двунаправленной.
template <class Т>' -* ”
class TreeNode : public d_Object {
Tvalue;
d_Ref<TreeNode<T> >parentNode;
d_Set<d_Ref<TreeNode<T> > > childNodes;
Этот метод позволяет приложению обходить дерево вверх и вниз.
Рекурсивные связи
Некоторые связи являются рекурсивными. Например, в древовидной структуре каждый узел содержит ссылку на родительский узел и ссылки на дочерние узлы. Рекурсия позволяет задать обход такого дерева в более лаконичном виде. Возьмем для примера класс Person.
father;mother; children;father;mother; children;class Person : public d_Object { public:
father;
mother; children;
father;
mother; children;
d_Ref<Person> d_Ref<Person> d_Set<d_Ref<Person> >
};
(Для упрощения примера данные здесь открыты, т.е. объявлены с использованием спецификатора доступа public.) Для доступа к “дедушке” (grandfather) некоторого лица можно было бы использовать следующее выражение.
d_Ref<Person> person;
// Инициализируем переменную person,
// чтобы она ссылалась на экземпляр класса Person. d_Ref<Person> grandfather = person->father->father;
Чтобы получить доступ к объекту-“дедушке” (grandfather), член father используется в обходе дважды. С помощью этого выражения пути для доступа к объекту назначения задается путь, “проходящий” через несколько взаимосвязанных объектов.
Интерфейсы связей
Связь представляется с помощью объектов, а для поддержки и обхода связи используются операции; причем поддерживаются как однонаправленные, так и двунаправленные связи.
На рис. 8.3 изображены используемые в этой главе классы и связи, существующие между ними. Каждому отделу соответствует некоторое множество служащих и проектов. Люди в отделе работают над несколькими проектами, а в каждом проекте обычно занято несколько человек. Каждый служащий может также иметь одного напарника, который представляется связью, направленной от экземпляра класса Employee назад к Employee. Такая связь называется рефлексивной (reflexive relationship).
Рис. 8.3. Модель для примера, иллюстрирующего связи между классамиРис. 8.3. Модель для примера, иллюстрирующего связи между классами
Рис. 8.3. Модель для примера, иллюстрирующего связи между классами
Рис. 8.3. Модель для примера, иллюстрирующего связи между классами
Эти связи можно представить с помощью однонаправленного или двунаправленного механизма связи. Класс Department мог бы иметь набор ссылок на объекты класса Project, Но каждый объект класса Project не может иметь обратную ссылку на соответствующий объект класса Department. Это — классический пример однонаправленной связи. При проектировании схемы базы данных необходимо определить, требуется ли переход по связям в обоих направлениях или нет.
Объявления однонаправленных связей в 0DMG
Следующий фрагмент программы предоставляет объявления однонаправленных связей для объектной модели, изображенной на рис. 8.3. Для представления однонаправленной связи не требуется никакого специального объекта связи. Вполне достаточно ссылок и коллекций ссылок.
class Department : public d_Object { public:
d_Set<d_Ref<Employee> >employees;
d_List<d_Ref<Project> >projects;
};
class Employee : public d_Object {
public:
d_Ref<Employee>office_mate;
d_Ref<Department>dept;
d_Set<d_Ref<Project> >projects;
};
class Project : public d_Object {
public:
d_Datecompletion() const;
};
Полное определение этих классов могло бы иметь другие данные- и функции- члены. Класс d_Ref<T> представляет связи типа “к-одному”, а классы d_Set<d_Ref<T» и d_List<d_Ref<T>> — неупорядоченные и упорядоченные связи типа “ко-многим” соответственно. Эти классы, описанные в главах 6 и 7, используются здесь для представления однонаправленных связей; в данном случае это никак не влияет на поведение классов.
Обратите внимание, что связь между классами Department и Employee имеет пути перехода на обоих концах, но связь между классами Department и Project имеет путь перехода только в направлении от класса Department к Project. Здесь показаны как однонаправленные, так и двунаправленные связи. Однако при использовании связей, объявленных так, как показано выше, в программе приложения должна быть предусмотрена поддержка ссылочной целостности между объектами.
Каждый объект в модели, на который будут ссылаться другие объекты, должен быть автономным перманентным объектом. Следовательно, коллекция-член employees класса Department должна содержать ссылки на класс служащих (d_Ref<Employee>), а не сами объекты класса Employee. Если бы объявление переменной employees имело вид
employees;,employees;,d_Set<Employee>
employees;,
employees;,
то объекты класса Employee были бы просто значениями структуры, встроенной в коллекцию. В этом случае они не были бы автономными объектами, на которые могли ссылаться другие объекты в базе данных.
Объекты связей ODMG
Стандарт ODMG определяет шаблонные классы для представления двунаправленных бинарных связей, которые автоматически поддерживаются программой ведения базы данных. Класс d_Rel_Ref<T, мт> представляет связь, характеризующуюся кардинальным числом “к-одному”. Класс d_Rel_Set<T, мт> представляет неупорядоченную связь, а класс d_Rel_List<T, мт> — упорядоченную. Приложение несет ответственность за поддержку порядка, основанного на относительной позиции элемента в списке. Эти классы (вместе с соответствующими базовыми классами) перечислены в табл. 8.1. Их интерфейс подробно описан в приложении Б. Все возможности базового класса — вставка, удаление и итерация по всем элементам коллекции — обеспечены в классе связи. Но эти операции также поддерживают ссылочную целостность.
Таблица 8.1. Классы двунаправленных связей ODMG
Класс связиБазовый класс
d_Rel_Ref<Т,
const
char ’
tMT>
d_
_Ref<T>
d_Rel_Set<T,
const
char ’
tMT>
d_
Set<d_Ref<T>>
d_Rel_List<T,
const
char
*MT>
d_
List<d_Ref<T>>
Эти классы связи используются попарно; класс на каждом конце двунаправленной связи должен иметь член, который является экземпляром одного из этих классов связи. Они имеют два шаблонных параметра:
т: класс на другом конце связи;
мт: имя строковой переменной, идентифицирующей член в классе т, который является членом обратного перехода по связи.
Лучший способ объяснить, как использовать эти классы, — переопределить связи в предыдущем примере.
На рис. 8.4 показаны двунаправленные связи, которые используются в остальных примерах программ настоящей главы. Рассмотрим связь между классами Department и Employee. Класс Department имеет член employees, который ссылается на экземпляры класса Employee, а в классе Employee содержится член dept, который ссылается на экземпляр класса Department. В объявлении члена employees второй шаблонный параметр, _dept, представляет собой строку, содержащую значение "dept". Эта строка ссылается на член dept класса Employee, который является членом обратного перехода. Аналогично в объявлении члена dept класса Employee второй шаблонный параметр, emps, — строка, содержащая значение "employees". Она ссылается на “свой” член обратного перехода, а именно член employees класса Department. Итак, члены переходов на обоих концах связи ссылаются один на другого.
Рис. 8.4. Модель двунаправленных связей
В следующих объявлениях классов используются объекты связи.
// Employee <-> Employee:// Employee <-> Department:// Department <-> Project:// Employee <-> Project:// Employee <-> Employee:// Employee <-> Department:// Department <-> Project:// Employee <-> Project:extern const char _officeMate[];
// Employee <-> Employee:
// Employee <-> Department:
// Department <-> Project:
// Employee <-> Project:
// Employee <-> Employee:
// Employee <-> Department:
// Department <-> Project:
// Employee <-> Project:
extern const char _dept[], _emps[];
extern const char _proj_dept[], _d_projs[];
extern const char _members[] , _projs[];
employees; projects;employees; projects;office_mate; dept;projects;office_mate; dept;projects;class Department : public d_Object ( public:
employees; projects;
employees; projects;
office_mate; dept;
projects;
office_mate; dept;
projects;
d_Rel_Set<Employee, _dept> d_Rel_List<Project, _proj_dept>
class Employee : public d_Object {
public:
d_Rel_Ref<Employee, _officeMate> d_Rel_Ref<Department, _emps> d_Rel_Set<Project, _members>
Объявленные (как показано выше файле (с расширением . с, . схх или .
) переменные инициализируются в программном . срр) следующим образом.
const
char
_officeMate[]=
"office_mate";
//
В
Employee
const
char
_dept[]=
"dept";
//
в
Employee
const
char
_em.ps [ ]=
"employees";
//
в
Department
const
char
_proj_dept[]=
"dept";
//
в
Project
const
char
_d_projs[]=
"projects"
//
в
Department
const
char
_members[]=
"members" ;
//
в
Project
const
char
_projs[]=
"proj ects";
//
в
Employee
class Project : public d_Object { public:
d_Rel_Ref<Department, _d_projs> d_Rel_Set<Employee, _projs>
dept;
members;
Каждый член перехода, используемый в связи, имеет соответствующую строковую переменную, которая содержит свое имя. Эта переменная применяется в качестве второго шаблонного параметра при объявлении члена обратного перехода на другом конце связи. Эти два конца “соединяются”, т.е. каждый конец ссылается на другой через свой второй шаблонный параметр.
Второй шаблонный параметр мт объявляется с помощью типа const char *. Причем это не шаблонный параметр типа, а шаблонный параметр значения. Компилятор использует именно адрес переменной, чтобы отличать реализации шаблонов. Для каждого конца связи в программе приложения может быть только одно такое значение. Эти объявления не будут работать, если второй шаблонный параметр будет представлен литеральным строковым значением. Если допустить такой вариант, то адрес литеральной строки будет другим в каждом модуле компиляции. Это заставит компилятор генерировать большое количество реализаций шаблона для данного члена перехода, но при этом должен быть создан только один экземпляр.
В реализации объектной базы данных для соединения двух концов связи используется спецификация члена обратного перехода, благодаря чему и реализуется поддержка этой связи. При модификации одного конца связи возможно автоматическое обновление второго.
Связь между двумя классами представляется данным-членом в каждом классе. Они неявно изменяются объектами связи (d_Rel_Ref<T, мт> и т.п.), когда приложение модифицирует другой конец связи. Следовательно, программа базы данных, изменяющая данное-член обратного перехода, не находится в области видимости класса, который содержит этот член. Рассмотрим, например, связь между классами
Department и Employee. При изменении члена dept класса Employee программа базы данных должна изменить член employees “в недрах” класса Department. Но программная процедура, обновляющая член employees, может не быть членом класса Department. Все зависит от того, где конкретная реализация базы данных размещает свои функциональные компоненты. Некоторые реализации добавляют свои функции- члены прямо в классы приложения (обычно это происходит во время процесса захвата схемы базы данных). Если реализация не делает этого, она может потребовать, чтобы эти данные-члены связи были объявлены открытыми (public). Но такое требование понижает уровень инкапсуляции. Поэтому разработчики должны сначала оценить возможности используемой реализации базы данных, а затем соответствующим образом проектировать свои классы.
Поддержка ссылочной целостности
Объекты связи, представленные в разделе 8.2.2, реализуют поддержку ссылочной целостности. Когда приложение модифицирует один конец связи, автоматически обновляется другой конец. Автоматическое обновление значительно уменьшает вероятность ошибок программирования, которые могут вызвать потерю ссылочной целостности.
Когда выполняется модификация на одном конце связи, блокировки накладываются на оба ее конца, поскольку модифицировать нужно оба конца. Большинство реализаций также требуют, чтобы при модификации объекты были активизированы в кэш-памяти приложения. Если реализация базы данных выполняет блокировку при гранулярности, большей, чем объект, то такая блокировка может вызвать проблему параллелизма. Эта проблема также существует, если приложение поддерживает связи вручную. Чтобы избежать наложения лишних блокировок и достичь более высокого уровня параллелизма, вместо двунаправленных связей можно использовать однонаправленные.
В следующих разделах рассматриваются сценарии, которые модифицируют связи между объектами. При этом предполагается использование схемы, определенной в разделе 8.2.2.
Сценарий использования связи типа прн-к-однпму
В первом сценарии используется рефлексивная связь office_mate, определенная в классе Employee. Предположим, что следующие переменные инициализируются таким образом, чтобы ссылаться на объекты с данными о служащих.
d_Ref<Employee> jennifer;
d_Ref<Employee> jeremy; d_Ref<Employee> brandon;
В качестве типа этих переменных можно также использовать тип Employee *. В реальной программе не следует использовать имя переменной для конкретного лица, в данном примере это сделано лишь в целях упрощения. Предположим, что ссылка jennifer указывает на служащую с именем Jennifer.
Предположим, вначале между служащими нет никаких связей (рис. 8.5). Переменная jennifer типа d_Ref<Employee> ссылается на экземпляр класса Employee с именем jennifer. Этот объект внутри каждого экземпляра класса Employee представляет член office_mate. На рис. 8.5 видно, что объекты класса Employee имеют атрибуты office_mate, которые пока не ссылаются на других служащих. Они имеют null-значения. - ,
119119Глава 8. Связи* а*
119
119
1ermifer:Employee
office mate
: d Re l Re f <Empl оу ее, office mate>
brandon:Employee
office mate
:d Rel Ref<Employee, office mate>
1 ererrty: Employee
office_mate
:d Rel Ref<Employee, office mate>
Рис. 8.5. Связи “один-к-одному" пока отсутствуют
Установка связи
Допустим, нам нужно сделать служащих jennifer и Jeremy напарниками по работе. Для этого можно использовать или оператор
jennifer->office_mate = jeremy;,
или оператор
jeremy->office_mate = jennifer;.
Приложением устанавливается только один конец связи. Второй конец обновляется автоматически программой базы данных.
После выполнения первой строки можно говорить о существовании связи (рис. 8.6). В этой главе используется стандартное UML-обозначение «арр» для указания, что соединение явно установлено приложением, и «db» — что объекты связи базы данных изменили член обратного перехода в ответ на действия приложения. Эти обозначения применяются для иллюстрации побочных эффектов поддержки связей. Кроме того, в последующие диаграммы не включены имена типов для объектов, чтобы сэкономить книжную площадь и отобразить больше объектов на рисунках.
Рис. 8.6. Установка связи “один-к-одному”Рис. 8.6. Установка связи “один-к-одному”
Рис. 8.6. Установка связи “один-к-одному”
Рис. 8.6. Установка связи “один-к-одному”
Модификация связи
Теперь предположим, что напарниками по работе стали Jeremy и brandon. Приложение выполняет следующую строку программного кода:
jeremy->office_mate = brandon;.
Результат выполнения этой программной строки показан на рис. 8.7. Атрибут office_mate служащей jennifer сейчас приобрел null-значение. В результате установки атрибута office_mate “от" служащего jeremy “к” служащему brandon следующие две операции выполняются автоматически, т.е. приложение в явном виде их не выполняет.
jennifer->office_mate.clear();
braridon->office_mate = jeremy;.
Поскольку приложение присвоило новое значение атрибуту office_mate служащего Jeremy, на ссылке к объекту jennifer, помеченному «арр», поставлен UML- маркер удаления (х).
Рис. 8.7. Модификация связи “один-к-одному”Рис. 8.7. Модификация связи “один-к-одному”
Рис. 8.7. Модификация связи “один-к-одному”
Рис. 8.7. Модификация связи “один-к-одному”
Удаление связи
Связь можно и удалить. Если установлена связь, показанная на рис. 8.6, после выполнения следующего оператора ссылка office_mate будет равна null-значению в объектах jennifer и Jeremy (рис. 8.8).
jennifer->office_mate.clear();jennifer->office_mate.clear();Рис. 8.8. Удаление связи "один-к-одному"Рис. 8.8. Удаление связи "один-к-одному"
jennifer->office_mate.clear();
jennifer->office_mate.clear();
Рис. 8.8. Удаление связи "один-к-одному"
Рис. 8.8. Удаление связи "один-к-одному"
Удаление связанного объекта
Предположим, у нас сложилась ситуация, показанная на рис. 8.7, и brandon удален из базы данных служащих. Теперь любые двунаправленные связи, в которых участвует brandon, должны быть обновлены, чтобы не существовало ни одной ссылки на удаленный объект. Приложение для удаления объекта brandon может выполнить либо строку
brandon.delete_obj ect() ;,
либо строки
Employee *b = brandon.ptr(); delete b;.
В результате создается ситуация, представленная на рис. 8.9. Ссылка office_mate в объекте Jeremy автоматически устанавливается равной null-значению. Значение ссылки на объект brandon больше недействительно, поскольку объект brandon удален из базы данных. Если бы эта ссылка не была установлена равной null-значению, она бы “повисла”, т.е. образовалась бы “повисшая” ссылка. Попытка использовать такую ссылку приведет к генерации исключения d_Error_Ref Invalid.
■jenniferoffice mate■jenniferoffice mateieremv«db»X brandonoffice_mate<x——Xoffice_mate«db»ieremv«db»X brandonoffice_mate<x——Xoffice_mate«db»«арр»«арр»
■jennifer
office mate
■jennifer
office mate
ieremv
«db»
X brandon
office_mate
<x—
—X
office_mate
«db»
ieremv
«db»
X brandon
office_mate
<x—
—X
office_mate
«db»
«арр»
«арр»
Рис. 8.9. Удаление объекта в связи “один-к-одному"
Главам. Связи :-^f
Сценарий использования связи типа один-ко-многим
В этом разделе иллюстрируется результат модификации связей “один-ко-многим”. Поскольку в этом случае одна сторона связи представляет собой коллекцию ссылок, а не одну ссылку, вставка и удаление элементов коллекции выполняется приложением в явном виде или программой базы данных в неявном виде.
Предположим, что существуют объекты и связи между ними, показанные на рис. 8.10. В этой ситуации используются следующие дополнительные переменные.
d_Ref<Department> d_Ref<Employee>d_Ref<Department> d_Ref<Employee>Department, sales;
d_Ref<Department> d_Ref<Employee>
d_Ref<Department> d_Ref<Employee>
dana, schaefer, Caroline, kristen;
Как отмечалось выше, эти переменные являются ссылками, и на последующих рисунках показаны объекты, на которые они ссылаются. Так же, как и в разделе 8.3.1, в рисунки не включены имена типов.
Рис. 8.10. Исходное множество связей "один-ко-многим”Рис. 8.10. Исходное множество связей "один-ко-многим”
Рис. 8.10. Исходное множество связей "один-ко-многим”
Рис. 8.10. Исходное множество связей "один-ко-многим”
Установка связи
Предположим, служащая kristen работает в отделе продаж (sales). С помощью следующей строки программы устанавливается соответствующая связь.
kristen->dept = sales;
После выполнения этой строки не только устанавливается ссылка dept объекта kristen на объект sales, но и kristen неявно добавляется к множеству служащих в объекте sales (рис. 8.11). Того же результата можно достичь путем прямого добавления объекта kristen в отдел (Department) продаж (sales).
sales->employees.insert_element(kristen) ;
Рис. 8.11. Установка связи “один-ко-многим"
Модификация связи
Допустим, что служащая dana переведена в отдел продаж (за!ез\ В этом случае приложение может выполнить следующий оператор.
dana->dept = sales;
Тогда программа базы данных автоматически выполнит следующие действия.
development->employees.remove_element(dana);
sales->employees.insert_element(dana);
Результат модификаций связей показан на рис. 8.12.
Рис. 8.12. Модификация связи “один-ко-многим’Рис. 8.12. Модификация связи “один-ко-многим’
Рис. 8.12. Модификация связи “один-ко-многим’
Рис. 8.12. Модификация связи “один-ко-многим’
Глава 8. Связи ' -у''
Удаление синаи
Теперь предположим, что служащая Caroline уволена из отдела продаж.
sales->employees.remove_element(Caroline) ;
При выполнении этой строки программы ссылка dept объекта Caroline неявно удаляется с помощью следующего оператора.
caroline->dept.clear();
Результат удаления связи показан на рис. 8.13.
Рис. 8.13. Удаление связи “один-ко-многим"
Удаление объекта
При удалении некоторого объекта все “замеченные” в связи с ним объекты должны быть обновлены, чтобы удалить их ссылки на этот объект. При связи типа один- ко-многим выполняются различные действия, в зависимости от значения кардинального числа на другом конце этой связи. В результате на обоих концах связи такого типа приложение удалит как дочерний, так и родительский объекты.
development.delete_object();
dana.delete_object();
В связи между классами Department и Employee объект development является родительским, а объект dana — дочерним.
На рис. 8.14 показан результат выполнения приложением обеих операций над связями, изображенными на рис. 8.13. При удалении объекта dana неявно удаляется только ссылка на объект dana в отделе продаж (sales). Но при удалении целого отдела разработки (development) удаляются ссылки dept всех объектов, ссылающихся на этот отдел. ' "
Рис. 8.14. Удаление объектов, участвующих в связи “один-ко-многим"
Сценарий использования связи типа многие-кп-многим
В этом разделе рассматривается манипулирование связями типа многие-ко-многим путем обновления связи между классами Employee и Project. Предположим, что существуют некоторые объекты и связи между ними (рис. 8.15). В этой ситуации на дополнительные объекты класса Project ссылаются следующие переменные.
d_Ref<Project>gui, database, communications;
VcmauoDKa связи
Подключим служащую Jennifer к проекту database. database->members.insегt_elament(jennifer);
В качестве альтернативного варианта можно использовать следующую запись.
jennifer->projects.insert_element(database) ;
При добавлении ссылки в один конец связи “многие-ко-многим” автоматически добавляется ссылка в другой конец. Если приложение выполняет один из этих операторов, другой неявно реализуется базой данных. Результат этих добавлений показан на рис. 8.16.
Мпдифииация связи
Для конца “ко-многим” связи “один-ко-многим” или “многие-ко-многим” применяется шаблон d_Rel_List или d_Rel_Set. При использовании шаблона d_Rel_Set модификация связи включает вставку элементов в коллекцию и удаление из нее. А с помощью шаблона d_Rel_List, который поддерживает позиционноотносительные операции обновления класса d_List, можно выполнить более сложные операции. Помимо вставки и удаления элементов в произвольной позиции списка, можно заменять элементы, расположенные на определенных позициях. Вставка элемента в класс d_Rel_Set была описана выше.
Пйвй8Свяэи ' ’ fIi ’'125
Рис. 8.15. Исходное множество связей “многие-ко-многим”
Рис. 8.16. Установка связи “многие-ко-многим”
Удапеиие связи
При удалении связи между двумя объектами, смоделированной с использованием класса d_Rel_Set, необходимо удалить элемент с другого конца этой связи. Предположим, служащего brandon сняли с проекта database с помощью следующего оператора.
database->members.remove_element(brandon) ;
Это приведет к изменениям, отраженным на рис. 8.17. Ссылка на объект brandon явно удаляется из коллекции members в экземпляре database класса Project. При этом из члена-коллекции projects в экземпляре brandon неявным образом удаляется ссылка на проект database.
Рис. 8.17. Удаление связи “многие-ко-многим”
Удаление связаншо объекта
Теперь рассмотрим эффект удаления объекта в связи “многие-ко-многим”. Это действие подобно удалению родительского объекта в связи “один-ко-многим”. Для отражения факта удаления родительского объекта обязательно обновляется каждый дочерний объект.
Предположим, мы завершили работу по разработке пользовательского интерфейса (GUI), и теперь нужно удалить объект, именуемый gui.
gui.delete_object();
Результат удаления показан на рис. 8.18. Поскольку служащие kristen и brandon имели ссылки на объект gui, их члены projects неявно обновляются для удаления этих ссылок."
Потенциально любой объект может участвовать во многих связях с различными классами. При удалении объекта должны быть удалены все ссылки на него в связан-
Гдавав. Связи , . .. .127
ных объектах. В этом (последнем) сценарии связанный объект department также будет обновлен, в результате чего исчезнет ссылка на объект ди! из члена projects.
Рис. 8.18. Удаление объекта в связи “многие-ко-многим”Рис. 8.18. Удаление объекта в связи “многие-ко-многим”
Рис. 8.18. Удаление объекта в связи “многие-ко-многим”
Рис. 8.18. Удаление объекта в связи “многие-ко-многим”
Обход связей и использование выражений пути
Путь обхода связей позволяет приложению опрашивать связанные объекты в базе данных. Как ссылки, так и коллекции являются фокусом оптимизации в объектных базах данных, а прямая их поддержка обеспечивается на физическом уровне. Их использование не требует обработки запросов или соединений. Объектные базы данных имеют значительное преимущество в производительности перед реляционными базами данных, которые должны динамически устанавливать связи (посредством обработки соединений) во время работы приложения.
Выражение, позволяющее выполнять переходы по нескольким объектам, называется выражением пути. Например, нам нужно вывести на печать информацию о проектах, в которых занят напарник данного служащего. Допустим, для класса Project определен оператор вывода ostream.
extern ostream ioperator« (ostream i, const Project &);
d_Ref<Employee> d_Iterator<Project> d_Ref<Project>d_Ref<Employee> d_Iterator<Project> d_Ref<Project>emp; // инициализируем эту переменную projs;
d_Ref<Employee> d_Iterator<Project> d_Ref<Project>
d_Ref<Employee> d_Iterator<Project> d_Ref<Project>
pro j;
projs = exnp->office .mate->projects . create_iterator () ; while(projs.next(proj) ){
. cout « * proj « endl;
Выражение, выделенное жирным шрифтом, — это и есть выражение пути.
В качестве альтернативного варианта предыдущий фрагмент программы можно записать с использованием библиотеки STL. Предположим, что следующая функция определена.
void
printProject(d_Ref<Project> proj) {
cout << * proj << endl;
}
Следующий фрагмент программы в STL-стиле выполняет те же действия, что и фрагмент, приведенный выше.
d_Ref<Employee> о mate = emp->office_mate;
for_each(o_mate->projects.begin(), o_mate->projects. end(), printProject);
Некоторые фирмы-производители поддерживают индексы пути, которые позволяют включить путь навигации. Путь обхода связей обычно задается с использованием синтаксиса языка запросов. Члены индекса пути могут включать объектные ссылки, коллекции объектных ссылок и даже функции-члены (в одной реализации). Завершающим членом в пути обычно является атрибут объекта. Например, следующее выражение пути связывается с классом Employee.
office_mate->proj ects->due_date
Описанный этим выражением путь “проходит” как через ссылку, так и коллекцию, прежде чем завершиться на атрибуте внутри класса Project.
Если компонент пути является коллекцией ссылок, внутри каждого элемента коллекции существует отдельная индексная запись. Индекс пути позволяет запросу эффективно отыскать все объекты, достижимые посредством конкретного пути обхода связей, в котором значение некоторого атрибута соответствует заданному условию.
Глава 9
Составные объекты
Объектная модель часто содержит объект, компоненты которого нельзя представить простыми атрибутами, встроенными в экземпляр. Эти компоненты лучше всего представляются в виде отдельных объектов. Если бы требовалось фиксированное количество экземпляров таких объектов, их можно было бы встроить во внешний объект, но очень часто возникает потребность именно в переменном количестве экземпляров. Тем не менее они считаются компонентами объекта, даже не являясь встроенными атрибутами. Составным называется объект, который состоит из подобъектов. Наряду с термином составной объект (composite object) в качестве синонима используется определение сложный объект (complex object).
Еше одна причина, по которой подобъекты должны представляться отдельными объектами, — между подобъектами часто существуют связи. Составной объект устанавливает контекст для своих подобъектов и их связей. “Значение" составного объекта базируется на его атрибутах, а также коллекции значений его подобъектов с их атрибутами и связями.
Типы, как в доменах, так и в категориях типов сущностей, моделируют абстракции, основополагающая реализация которых включает объединение компонентов. Составная сущность — это еще одна категория типов, и ее отличительная особенность в том, что она представляет собой объединение других объектов. Эти подобъекты могут быть экземплярами классов сущностей или составными объектами. Такая организация позволяет создавать многоуровневую иерархию вложенности объектов.
Термин составной объект используется для определения обобщенной абстракции. Конкретный объект, называемый в этой книге составным базовым объектом, представляет характеристики, которые применяются к составному объекту в целом. Составной базовый объект оснащен средством перемещения по всем своим составляющим подобъектам. Этот объект и все его подобъекты вместе образуют составной объект.
Удивительно, но большинство объектных баз данных еще не поддерживают в явном виде составные объекты, хотя производители часто говорят о том, что их продукт обеспечивает более качественную поддержку составных объектов, чем реляционные базы данных. На самом деле они обеспечивают основной механизм, необходимый для организации множества связанных объектов, а приложения могут использовать этот механизм для обеспечения эффективного управления составными объектами.
Примеры составных объектов
В примере с объектами, служащими и проектами каждый из этих объектов мог быть составным. Класс Department (отдел) имеет множество проектов, находящихся в состоянии разработки, а также множество процессов разработки, завершенных для каждого проекта. Этот класс также имеет множество объектов (служащих) класса Employee, но мы предполагаем, что служащие являются компонентами всей компании, а не одного отдела. При ликвидации отдела служащие не увольняются, а переводятся в другие отделы.
Экземпляры класса Project, связанные с некоторым отделом, считаются подобъектами соответствующего экземпляра класса Department. Класс Project сам может быть составным объектом. Он может иметь подобъекты, которые представляют этапы процессов, необходимые для завершения проекта. Каждый этап процесса имеет ответственного исполнителя, а также даты планируемого и реального завершения. Связи между упомянутыми классами показаны на рис. 9.1.
Рис. 9.1. Пример составного объекта (класс Project)Рис. 9.1. Пример составного объекта (класс Project)
Рис. 9.1. Пример составного объекта (класс Project)
Рис. 9.1. Пример составного объекта (класс Project)
Классы Department и Project являются классами составных объектов. Связи между этими классами можно представить с помощью приведенных ниже объявлений классов. Классы Department и Employee и их связи описаны в главе 8. Классы Project и Process имеют экземпляры, которые являются подобъектами класса Department. Класс ProjectProcessStep имеет экземпляры, которые представляют собой подобъекты класса Project.
// Employee <-> Department: // Department <-> Project: // Employee <-> Project:// Employee <-> Department: // Department <-> Project: // Employee <-> Project:extern const char _dept[], _emps[);
// Employee <-> Department: // Department <-> Project: // Employee <-> Project:
// Employee <-> Department: // Department <-> Project: // Employee <-> Project:
extern const char _proj_dept[], _d_projs[J;
extern const char _members[] , _projs[);
projects;employees;projects;employees;processes;processes;class Department : public d_Object ( d_Rel_List<Project, _proj_dept> d_Rel_Set<Employee, _dept>
projects;
employees;
projects;
employees;
processes;
processes;
d_List<d_Ref<Process> > // Другие члены.
);
class Employee : public d_Object ( d_Rel_Ref<Department, _emps>dept;
d_Rel_Set<Project, _members>projects;
const char const char const char const char const char const charconst char const char const char const char const char const char_dept[] _emps[] _proj_dept[] _d_proj s[] _members[ ] _projs[]_dept[] _emps[] _proj_dept[] _d_proj s[] _members[ ] _projs[]= "dept";= "employees";= "dept";= "projects"= "members";= "projects";= "dept";= "employees";= "dept";= "projects"= "members";= "projects";// в Employee// в Department// в Project// в Department// в Project// в Employee// в Employee// в Department// в Project// в Department// в Project// в Employee
const char const char const char const char const char const char
const char const char const char const char const char const char
_dept[] _emps[] _proj_dept[] _d_proj s[] _members[ ] _projs[]
_dept[] _emps[] _proj_dept[] _d_proj s[] _members[ ] _projs[]
= "dept";
= "employees";
= "dept";
= "projects"
= "members";
= "projects";
= "dept";
= "employees";
= "dept";
= "projects"
= "members";
= "projects";
// в Employee
// в Department
// в Project
// в Department
// в Project
// в Employee
// в Employee
// в Department
// в Project
// в Department
// в Project
// в Employee
Отдел для каждого проекта выполняет множество этапов процесса. Этапы процесса могут быть различны для разных отделов, поэтому каждый отдел имеет собственный список. Класс Process содержит обобщенную информацию о процессе, которая не связана с конкретным отделом.
class Process d_String d_String / / и т. д.class Process d_String d_String / / и т. д.public d_Object { name ;
class Process d_String d_String / / и т. д.
class Process d_String d_String / / и т. д.
description;
Приложение не испытывает потребности в переходе от экземпляра класса Process к каждому связанному с ним экземпляру класса ProjectProcessStep, поэтому этот путь перехода не предусмотрен. Класс ProjectProcessStep имеет член с именем process, который ссылается на экземпляр класса Process. Каждый проект должен поддерживать информацию об этапах процесса. Каждый экземпляр класса Project имеет экземпляры класса ProjectProcessStep для каждого экземпляра класса Process в члене processes своего отдела (класса Department).
dept;members;processsteps;dept;members;processsteps;class Project : public d_Object { d_Rel_Ref<Department, _d_projs> d_Rel_Set<Employee, _projs> d_List<d_Ref<ProjectProcessStep> >
dept;
members;
processsteps;
dept;
members;
processsteps;
// Другие члены.
class ProjectProcessStep : public d_Object (
d_Ref<Process>process;
d_DateexpectedCompletion;
d_Dateactualcompletion;
d_Ref<Employee>owner; // служащий, ответственный за этап
// Другие члены.
Класс Project считается составным объектом, поскольку он управляет множеством экземпляров класса ProjectProcessStep. Приложение получает доступ к экземплярам класса ProjectProcessStep только через член processsteps класса Project. Сначала приложение получает ссылку на экземпляр класса Project, а затем обращается к связанным экземплярам класса ProjectProcessStep. В этом случае у класса ProjectProcessStep не было необходимости в обратной ссылке на связанный с ним экземпляр класса Project, поскольку приложение уже получило эту ссылку алгоритмическим путем.
Подобъекты
Подобъект — это компонент составного объекта. Объект, встроенный в качестве члена другого класса, не считается подобъектом. Подобъект может быть простым или составным объектом, т.е. при создании составных объектов можно предусмотреть многоуровневую иерархию подобъектов произвольной сложности.
Объект считается подобъектом, если его существование зависит от составного базового объекта, т.е. он не может существовать без “своего” составного объекта. Когда приложение удаляет составной объект, должны быть удалены составной базовый объект и все его подобъекты. Приложение применяет операцию удаления к составному базовому объекту, а деструктор составного базового объекта несет ответственность за распространение операции удаления на его подобъекты.
Класс Employee представляет собой составной объект с подобъектами. Каждый экземпляр класса Employee содержит информацию о пособиях служащим — медицинская страховка, договор о страховании жизни, планы образования сбережений, оплата счетов расходов на медикаменты, уход за детьми и престарелыми.
Предположим, о каждом иждивенце необходимо хранить такие данные, как имя, дата рождения и степень обеспечения (рис. 9.2). Экземпляр класса Benefits является подобъектом экземпляра класса Employee, а экземпляр класса Dependentcoverage — подобъектом класса Benefits. Однако для представления связи между классами Benefits и Dependentcoverage существует альтернатива. Класс Benefits может иметь коллекцию, содержащую элемент для каждого иждивенца. Но вместо типа элемента-ссылки на экземпляр класса Dependentcoverage используется сам экземпляр.
class Dependentcoverage d_String d_Stringd_Date// Варианты расходов наclass Dependentcoverage d_String d_Stringd_Date// Варианты расходов на: public d_Object {last_name;
class Dependentcoverage d_String d_String
d_Date
// Варианты расходов на
class Dependentcoverage d_String d_String
d_Date
// Варианты расходов на
first_name; date_of_birth;
медикаменты и медобслуживание.
class Benefits ; public d_Object { // Информация о пособиях служащего.
d_Vaггау<DependentСоverage> dependentCoverages; };
class Employee : public Person { d_Ref<Benefits>benefits;// Все остальные члены класса Employee.class Employee : public Person { d_Ref<Benefits>benefits;// Все остальные члены класса Employee.dependentcoveragesdependentcoveragesРис. 9.2. Пример составного объекта (Employee)Рис. 9.2. Пример составного объекта (Employee)
class Employee : public Person { d_Ref<Benefits>benefits;
// Все остальные члены класса Employee.
class Employee : public Person { d_Ref<Benefits>benefits;
// Все остальные члены класса Employee.
dependentcoverages
dependentcoverages
Рис. 9.2. Пример составного объекта (Employee)
Рис. 9.2. Пример составного объекта (Employee)
Экземпляры класса Dependentcoverage хранятся как встроенные значения внутри коллекции dependentcoverages. Поскольку они не являются автономными перманентными подобъектами, эта связь не квалифицирует экземпляр класса Benefits как составной объект.
В результате можно сделать несколько выводов. На экземпляры класса Dependentcoverage не могут напрямую ссылаться другие объекты базы данных (они представляют собой значения, встроенные в коллекцию). Однако на экземпляры класса ProjectProcessStep ссылки вполне допустимы. Экземпляры класса Dependentcoverage активизируются автоматически с помощью соответствующего экземпляра класса Benefits, в то время как экземпляры класса ProjectProcessStep автоматически не активизируются. Использование ссылок вместо встроенных значений объектов означает, что для доступа к объектам необходимо выполнить больше операций разыменования. Если реализация накладывает блокировки на базис объекта, то для экземпляра класса Benefits и всех содержащихся в нем экземпляров класса DdpendentCoverage требуется только одна блокировка. Но для экземпляра класса Project и его экземпляров класса ProjectProcessStep по-
требовались бы отдельные блокировки. При проектировании объектной модели все эти факторы нужно обязательно учитывать. Какому варианту отдать предпочтение — зависит от требований, предъявляемых к конкретному приложению.
Ссыпки на объекты, которые не являются нодобъектами
Объект, содержащий ссылку (d_Ref<T>) или коллекцию ссылок на другой объект типа Т, необязательно является составным. Многие объекты, не являющиеся составными, содержат ссылки на другие объекты. И точно так же составной объект, который включает подобъекты, может содержать ссылки на объекты, не являющиеся его подобъектами. Эти адресуемые объекты не считаются компонентами данного составного объекта, и на них не оказывает влияние удаление составного объекта.
В описанном выше приложении содержалось несколько примеров таких включений. В классе ProjectProcessStep есть член process, который представляет собой ссылку на экземпляр класса Process, и член owner, который ссылается на экземпляр класса Employee. Ни один из этих адресуемых объектов не считается подобъектом экземпляра класса ProjectProcessStep. Точно так же класс Department содержит коллекцию ссылок на экземпляры класса Employee, именуемую employees. В этой объектной модели экземпляры класса Employee не являются подобъектами экземпляров класса Department, несмотря на то что Department — составной объект, содержащий подобъекты, которые имеют отношение к проектам и процессам.
Составной базовый объект
Составной объект представляет абстракцию, которая имеет соответствующий набор операций. Операции, специфичные для данного составного объекта, обычно определяются внутри класса составного базового объекта. Причем их реализация зачастую включает необходимый набор манипуляций подобъектами. Составной базовый объект должен выступать в роли атомарного объекта, представляющего моделируемую составную абстракцию. Цель определения составного объекта — добиться высокой степени атомарности, даже если объект представляет собой объединение других объектов.
Составной базовый объект часто служит в качестве корневого (root), или входного (entry point) объекта для множества взаимосвязанных подобъектов. Эти альтернативные термины часто используются фирмами-производителями программных продуктов, когда они говорят о составных базовых объектах. Конечным пользователям часто разрешается присваивать составному объекту некоторое имя, причем обычно оно относится именно к составному базовому объекту. Если же доступ к последнему осуществляется по имени, то для доступа к его подобъектам (для выполнения операций над составным объектом в целом) используются возможности навигации.
Подобъекту можно присвоить уникальное имя, связанное с его составным объектом, который таким образом будет играть роль области видимости имени (name scope) для его подобъектов. Стандарт ODMG присваивает имена объектам на основе соглашений, принятых для баз данных, поэтому имя составного объекта можно использовать в качестве префикса для имени подобъекта. Составной базовый объект может также иметь один или несколько словарей, которые преобразуют имя в подобъект. В этом случае словари можно применять как механизм коллекций, предназначенный для доступа к подобъектам.
0.2. Физическая организация составных объекта»
Составные базовые объекты для ссылки на свои подобъекты используют либо однонаправленные, либо двунаправленные связи. Главное — обеспечить возможность перехода от составного базового объекта ко всем его подобъектам. Обычно приложе-
ние сначала получает доступ к составному базовому объекту, а затем переходит к нужному подобъекту, запоминая ссылку на составной базовый объект. Следовательно, вам не нужно хранить в базе данных ссылку, чтобы перейти из подобъекта в составной базовый объект. Для этого вполне достаточно однонаправленной связи.
Составные объекты служат естественной основой для принятия решения о кластеризации (группировании) и разделении объектов в базе данных. Многие реализации объектных баз данных позволяют приложению выполнять кластеризацию объектов. К подобъектам составного объекта часто осуществляется доступ как к отдельным группам. Приложения одновременно могут получать доступ только к одной группе подобъектов. Тогда, вероятно, имеет смысл кластеризовать объекты в некоторую группу, но отделить ее от других групп. Принимая решение о кластеризации или разделении объектов, следует помнить о связях составных объектов и способах доступа, используемых приложением. .
Рассмотрим иерархию составных объектов, показанную на рис. 9.3. На рис. 9.4 иллюстрируется начальное группирование подобъектов “в глубину”. Группы показаны на двух уровнях иерархии. Уровень потенциальной кластеризации зависит от количества объектов, которые можно кластеризовать в блоке, и уровня параллелизма, необходимого на первом (нижнем) уровне.
На рис. 9.5 показано начальное группирование подобъектов “в ширину". То, какая из представленных двух стратегий кластеризации лучше, зависит от способа доступа, используемого приложением.
В нашем примере экземпляры класса ProjectProcessStep можно сгруппировать со связанным с ними составным базовым объектом класса Project. Более того, экземпляры Project и Process можно сгруппировать со связанным с ними экземпляром класса Department. Объекты класса Dependentcoverage автоматически группируются с их экземпляром класса Benefits, если они встроены внутри члена dependentСоverages.
Приложения часто манипулируют составным объектом как одним целым. Отдельные составные объекты разделяются, если они не используются вместе как подобъекты некоторого составного объекта более высокого уровня.
В идеале, дисковые блоки должны содержать подобъекты только одного составного объекта. Кластеризация подобъектов внутри составного объекта с последующим отделением каждого составного объекта минимизирует количество дисковых блоков, необходимых для доступа к составному объекту. Если подобъекты нескольких составных объектов окажутся перемешанными, то в передачах данных будет задействовано больше дисковых блоков, что усложнит управление кэш-памятью базы данных.
Разделение составных объектов должно увеличить степень параллелизма, зависящую от дискретности блокирования в данной реализации. Приложения, использующие реализации с постраничной блокировкой или блокировкой на уровне сегментов,
должны иметь гарантию, что составные объекты, требующие параллельный доступ, не содержат подобъектов, которые перемешаны внутри одних и тех же областей блокирования. В противном случае приложения при доступе к таким подобъектам могут попасть в тупик.
Рис. 9.4. Группирование подобъектов “в глубину” в иерархии составных объектов
Рис. 9.5. Группирование подобъектов "в ширину” в иерархии составных объектов
В нашем примере приложения имеет смысл разделить составные объекты класса Department. Следует также разбить данные, связанные с проектами и процессами для двух различных отделов, поскольку к ним не будет выполняться одновременный доступ со стороны одного приложения. В результате это уменьшит вероятность возникновения конфликтов блокировок между приложениями, обслуживающими различные отделы.
К данным о пособиях для служащих, скорее всего, не будет частых обращений. Поэтому рациональнее было бы сгруппировать все экземпляры класса Employee в самой маленькой области хранения. В данном случае имеет смысл отделить экземпляр класса Benefits от связанного с ним экземпляра класса Employee, уменьшив тем самым объем кэша базы данных, необходимого для доступа ко всем экземплярам класса Employee.
Нередко требуется быстро получить доступ к конкретному подобъекту составного объекта. Иногда приложение обеспечивает прямую поддержку такого доступа с помощью словаря, который предоставляет ссылку на искомый подобъект посредством заданного идентифицирующего значения. Для поиска определенного подобъекта можно также использовать индекс пути.
Операции
Следствием операций, применяемых к составному объекту, часто является их применение и к подобъектам. Такую ситуацию иногда называют распространением операции. Некоторые применяемые к составному объекту операции, например удаление, распространяются на все его подобъекты.
Некоторые реализации объектных баз данных для доступа к адресуемым объектам предоставляют операцию замыкания (get-closure). Ее можно использовать для активизации всех (или некоторого подмножества) подобъектов составного объекта. В результате можно сделать один запрос к базе данных, передав полный набор подобъектов в виде группы, вместо последовательного доступа к объектам (поочередно к каждому), что, конечно же, менее эффективно.
Для составного объекта должна быть определена семантика наиболее популярных операций (по примеру канонической формы в языке C++). Дизайнер класса должен решить “судьбу” этих операций, т.е. сделать их или поверхностными, или глубинными. В данном случае нужно ответить на вопрос, должны ли они применяться как к составному базовому объекту, так и к его подобъектам? Функции в канонической форме C++ (копирование, присваивание, удаление) обычно являются глубинными операциями.
Подобъект зависит от включающего составного базового объекта. Таким образом, когда к составному базовому объекту применяется операция копирования, реализуемая конструктором копии, создается копия каждого подобъекта. Предположим, существует два экземпляра, а и ь, класса составного объекта. В результате присваивания экземпляра Ь экземпляру а будут удалены текущие подобъекты экземпляра а и создана копия каждого подобъекта экземпляра ь.
а = Ь;
Это свидетельствует о том, что при манипуляции составными объектами обычно требуются глубинные операции.
Но при проектировании составного объекта возможно решение, согласно которому операции будут затрагивать только составной базовый объект или некоторое подмножество составного объекта. Каждый проект составного объекта требует инженерной оценки для определения области действия операции, применяемой к составному объекту (в смысле распространения на его подобъекты).
По определению удаление составного базового объекта должно быть распространено на все его подобъекты. Однако не следует забывать, что составной объект может содержать ссылки на объекты, которые не являются его подобъектами и поэтому не должны удаляться. При проектировании составного объекта нужно для каждой ссылки определить, является ли адресуемый ею объект компонентом данного составного объекта. За надлежащее управление подобъектами несет ответственность только реализация данного класса.
В рассмотренном выше примере классы Department, Employee и Project являются составными объектами. Их деструкторы обеспечивают надлежащее удаление соответствующих подобъектов.
Project::-Project()
{
d_Iterator<d_Ref<ProjectProcessStep> > iter;
d_Ref<ProjectProcessStep> pps;
iter = processSteps.create_iterator() ;
while( iter.next(pps) )
pps.delete_object();
)
Employee::-Employee()
if( !benefits.is_null() ) benefits.delete_object();
}
Department: : -Department() {
d_Iterator<d_Ref<Process> > iter_process; d_Iterator<d_Ref<Project> > iter_project; d_Ref<Process> proc;
d_Ref<Project> proj ;
iter_process = processes.create_iterator(); while( iter_process.next(proc) )
proc.delete_object();
iter_project = projects.create_iterator() ;
while( iter_project.next(proj) ) proj.delete_obj ect();
}
Некоторые базы данных позволяют распространять блокировки, хотя это и не поддерживается в стандарте ODMG. Дизайнер схемы базы данных должен определить, подразумевается ли под блокированием составного объекта блокировка составного базового объекта и всех его подобъектов. Существуют базы данных, в которых при блокировании данного объекта блокируется все множество связанных объектов. В этом случае для продолжения работы приложения необходимо успешное наложение блокировки на объект с не менее успешным распространением блокировок связанных объектов. Объект, косвенно заблокированный за счет распространения блокировок, сам может распространить “инфекцию’’ блокирования на другие объекты. Таким образом, распространение блокировок может охватить большое количество объектов и тем самым ограничить параллелизм операций.
Другой метод, который может использовать приложение, — установить некоторые правила блокирования при доступе к составному объекту. В соответствии с этими правилами каждое приложение перед доступом к подобъектам накладывает блокировку на составной базовый объект, что можно расценивать как неявную блокировку всех подобъектов. При использовании некоторых реализаций объектных баз данных приложениям можно получать доступ к подобъектам без наложения на них блокировок. Но это действенно только в том случае, если все приложения следуют этим правилам.
Резюме
Домен представляет самую примитивную категорию типа, которую можно определить в объектной базе данных. Свойство перманентности приобретается при встраивании объекта в качестве члена класса, который представляет некоторую сущность. В реализации ODMG представляющий сущность класс, выведенный из класса d Object, обладает как перманентностью, так и возможностью идентификации. Наконец, составной объект — самая сложная категория типа, которую можно определить в объектной базе данных. Он определяет абстракцию, охватывающую объединение отдельных перманентных экземпляров сущностей. Домены, сущности и составные объекты представляют прогрессивные уровни объединения данных в базе данных. Для уменьшения уровня сложности используется инкапсуляция, благодаря чему возможно создание объектной модели, имеющей несколько уровней объединения.
Этой главой мы завершаем рассмотрение объектных моделей C++ для объектной базы данных. В части II описан язык объектных запросов стандарта ODMG.
Часть II
Язык объектныхзапросов
Глава 10
Введение в язык объектных запросов
Эта часть книги посвящена описанию языка объектных запросов (Object Query Language — OQL). Главная цель OQL — обеспечить доступ декларативных запросов к объектам в объектной базе данных, поддерживая объектную модель приложения, определенную посредством классов в схеме объектной базы данных.
OQL-запрос можно задать либо программным способом (внутри С++-программы приложения), либо сам пользователь может построить специальный запрос с помощью командной строки, среды оболочки, среды, ориентированной на язык четвертого поколения (fourth-generation language — 4GL), или графического интерфейса пользователя (graphical user interface — GUI). Язык 4GL или среда GUI могут предоставить дружественный к пользователю интерфейс для задания запросов и просмотра полученных результатов.
В приложениях, ориентированных на C++, для работы с базами данных часто используется непосредственный навигационный интерфейс C++. Те, кто “в первых рядах” принял технологию объектных баз данных, часто выдвигали строгие требования к производительности, которым не отвечали реляционные базы данных. У реляционных баз данных есть единственный интерфейс с базой данных; язык SQL. Поначалу в объектных базах данных на первый план выдвигалась бесшовная плотная интеграция с языком C++ в ущерб поддержке языка запросов. В некоторых сферах приложений, использующих объектные базы данных, доступ посредством языка запросов вообще не считался чем-то важным и заслуживающим внимания. Нужды этих приложений, обратившихся к использованию объектных баз данных, на первых порах удовлетворялись С++-средствами прямого навигационного доступа.
Но по мере применения объектных баз данных к более широкому спектру прикладных областей, разработчики поняли, что никак нельзя обойтись как без ассоциативного, так и навигационного доступа к базам данных. И тогда поддержка языка запросов приобрела существенное значение. В среде клиент/сервер оказалось рациональным подавать декларативный запрос для анализа на отдельный компьютер сервера и возвращать приложению лишь результаты этого запроса. Такой подход эффективен только в том случае, если архитектура базы данных поддерживает обработку запросов в кэш-памяти базы данных. Иногда навигационный доступ приводит к ненужным передачам объектов от сервера к клиенту. Предположим, что приложение активизировало некоторый объект. Приложению, возможно, нужны другие объекты, которые достижимы только путем выполнения многоуровневой навигации, а промежу-
точные объекты в пути перехода мотуг потребоваться только в целях навигации. Запрос, задающий путь перехода, можно передать серверу базы данных, который способен вернуть клиенту только интересующие его объекты. Если бы приложение само выполняло такую навигацию, то все промежуточные объекты, задействованные в пути перехода, пришлось бы активизировать в приложении, хотя они ему и не нужны. Кроме того, язык запросов может обеспечить более удобный интерфейс для описания ассоциативного доступа на основе спецификации условий, задаваемых для значений атрибутов. И снова-таки, если сервер базы данных может выполнять обработку запросов, этот метод позволяет передавать только те объекты, которые действительно необходимы приложению.
Для конечного пользователя интерфейс специальных запросов является единственным механизмом доступа к данным, имеющимся в его распоряжении. Значительное преимущество реляционных баз данных — существование стандартного реляционного языка запросов SQL, который можно использовать особым способом. Пользователи часто хотят иметь простой в применении механизм для запроса и анализа своих данных и при этом не зависеть от программистов. В этом случае пользователи могут избежать длительных ожиданий, связанных с долгосрочным циклом разработки программных продуктов, что позволит им сэкономить как время, так и деньги.
Главы 11 и 12 содержат полное описание языка OQL. В главе 11 описаны основные выражения и операторы, а в главе 12 вы узнаете, как создавать запросы к коллекциям с помощью предикатов. Глава 13 рассматривает способы выполнения OQL- запросов внутри C++-приложения.
Задачи 001
Язык OQL разработан Франциском Бенсилхоном (Frangois Bancilhon), Софи Клуэ (Sophie Cluet) и Клауди Дилоубел (Claude Delobel), компания Q2 Technology, и принят консорциумом ODMG в качестве стандартного языка объектных запросов. Ориентированный на объектную модель ODMG, язык OQL поддерживает систему объектных типов, определенную в схеме базы данных. Он также непосредственно поддерживает в объектной модели конструкции инкапсуляции, наследования и полиморфизма, определенные в модели приложения. При этом поддерживается как ассоциативный, так и навигационный доступ к базе данных. Язык OQL обеспечивает супермножество тех запросов языка SQL-92, которое является общепринятым интерфейсом, поддерживаемым производителями реляционных баз данных.
Языку OQL не предначертана роль главного или эксклюзивного интерфейса с базой данных, и, кроме того, он не имеет собственной системы типов для представления данных. Он действует в контексте системы типов объектной модели, определенной приложением. В С++-среде базы данных система типов опирается на классы C++, которые определяют схему базы данных. В среде разработки приложений на Java или Smalltalk эти языки должны установить свою систему типов для базы данных.
OQL-метод резко отличается от SQL-метода, используемого реляционными базами данных. В этих средах систему типов определяют SQL и реляционная база данных, причем SQL является единственным интерфейсом с базой данных. Это вызывает необходимость преобразования между типами, известными языку SQL, и аналогичными типами в языке программирования приложения.
При использовании объектной базы данных приложения и сама база данных разделяют единую систему типов. Кроме того, существует высокая степень интеграции с объектно-ориентированным языком программирования, что создает возможность для тесного взаимодействия приложений и языка запросов. Например, язык OQL позволяет получать прямой доступ к функциям, определенным в объектной модели. Функции-члены C++ можно вызывать в OQL-выражениях, чтобы выполнять действия над объектами в базе данных. ,
При проектировании в язык OQL не закладывались мощные вычислительные возможности. Для задания определений функций и объектных методов, используемых в запросах, язык OQL опирается на приложение, написанное на объектноориентированном языке программирования. Выражения запросов строятся на небольшом наборе операторов запросов, определенных в этом языке.
Язык OQL предоставляет операторы, пригодные лишь для реализации доступа запросов к объектной базе данных (для изменения объектов операторы обновления не предусмотрены). Разрешение непосредственно обновлять атрибуты можно расценить как нарушение принципов инкапсуляции. В то же время язык OQL позволяет выполнять над объектами некоторые операции, определенные в приложении. Такие операции (которые как раз и могут обновлять объекты) представлены либо функциями-членами C++, либо независимыми функциями, не связанными с конкретным классом. Они обеспечивают правильную семантику обновления, основанную на представлении и реализации данного объекта. Таким образом, язык OQL поддерживает инкапсуляцию, определенную в объектной модели, с помощью операций, предусмотренных дизайнером класса только для изменения состояния объекта. Это именно то, что можно было ожидать от объектно-ориентированного языка запросов. Причем эти функции реализованы в том же языке, который использован для определения объектной модели.
Многие разработчики приложений, работающие в SQL-среде, не желают, чтобы пользователи самостоятельно обновляли базу данных напрямую, беспокоясь о возможном разрушении данных. Они зачастую ограничивают возможности обновления в специальной среде SQL. Для предотвращения обновлений, способных вызвать некорректные и несогласованные изменения данных, часто используются условия сохранения семантической целостности. В языке OQL, в отличие от SQL, не предусмотрен оператор update, но приложение может создать доступные в среде запросов функции и объектные методы для обновления данных. Такой подход обеспечивает большую гарантию целостности данных, чем метод, используемый в SQL-средах.
Язык OQL был адаптирован для принятия в коммерческих кругах, в которых доминирующие позиции до сих пор занимает SQL. В результате некоторых корректировок была повышена степень совместимости с SQL. Ныне язык OQL довольно близок к поддержке спецификации входного уровня ANSI SQL-92, являющейся в действительности спецификацией SQL версии SQL-89, на которую “настроено” большинство реляционных продуктов. Сходство между этими языками запросов должно облегчить переход уже существующих SQL-продуктов, а вместе с ними и обслуживающего персонала, знакомого с SQL, на “рельсы” OQL. При этом следует иметь в виду, что OQL предлагает множество возможностей, которых попросту нет в языке SQL.
OQL - функциональный язык запросов
OQL — функциональный язык запросов, который статически типизирован. Каждое выражение запроса состоит из оператора, применяемого к нескольким операндам (их количество может равняться нулю) определенного типа. Выражение запроса возвращает значение вполне конкретного типа. Результат запроса может иметь любой тип. И наоборот, результат каждого запроса SQL представляет собой таблицу кортежей. Способность языка OQL возвращать значение любого типа разрешает пользователям при построении выражений запросов более эффективно применять композицию, составленную из функций.
Контроль типов выполняется при анализе запроса, и при обнаружении ошибки, связанной с использованием типов, запрос возвращает значение ошибки. В среде OQL должно присутствовать определение для каждого типа, используемого в OQL- выражении. Типы обычно определяются средой языка программирования приложения (C++) и схемой базы данных.
Для построения запросов произвольной сложности используется композиция функций. Операнд может быть либо литеральным значением, либо другим выражением запроса произвольной сложности. Каждое действительное выражение запроса представляет собой законченный запрос, который также может служить операндом для другого выражения запроса (предположим, что он возвращает значение допустимого типа). Такая организация позволяет строить как простые, так и довольно сложные запросы.
Предположим, что оператор F имеет единственный операнд типа То и возвращает значение типа Ti. Также предположим, что существует оператор G, который имеет операнд типа Ti и возвращает значение типа т2. С учетом композиции функций выражение G(F(t0) ) является действительным выражением запроса, которое преобразует значение t0 типа т0 в некоторое значение типа т2 посредством вызова оператора F с операндом t0 и последующего вызова оператора G с использованием в качестве его операнда значения, возвращенного оператором F.
Язык OQL поддерживает как объекты (с возможностью идентификации в базе данных), так и значения (которые лишены идентификации). Он поддерживает все рассмотренные выше категории типов, включая домены, сущности и составные объекты.
Глава 11
Выражения и операторы языка OQL
Язык OQL состоит из операторов запросов, которые могут объединяться посредством композиции функций. Поскольку каждое действительное выражение запроса может служить в качестве операнда для другого выражения, можно строить довольно сложные выражения запросов.
OQL — строго типизированный язык. Каждое выражение запроса принимает операнды определенных типов и возвращает результат вполне конкретного типа. Контроль типов выполняется при анализе запроса. Иногда тип результата запроса определяют типы операндов, участвующие в выражении запроса. Тип выражения запроса выводится из структуры выражения запроса, объявлений типов в схеме, а также типов литералов и именованных объектов.
Описанию операторов запроса посвящены эта и следующая главы. В описании указывается тип возврата оператора и требуемые типы операндов. Каждый пример представляет законченный запрос, который может использоваться в качестве операнда для другого выражения запроса.
Для представления типизированных выражений, служащих в качестве операндов оператора запроса, используются некоторые символические имена. Они (вместе с указанием типа данных) перечислены в табл. 11.1. Эти символы в описаниях OQL- операторов означают операнды. Когда в качестве операнда используется больше одного выражения данного типа, применяется индекс. Операнд может быть либо простым литеральным значением, либо сложным, глубоко вложенным выражением запроса.
Таблица 11,1. Символические обозначения
Символ
Предстввляемое OQL-выражение
attr
Атрибут объекта
bool
Булево выражение
char
Односимвольное выражение
coll
Выражение коллекции
diet
Выражение словаря
expr
Произвольное OQL-выражение
Окончание табл. 11.1
Символ
Представляемое OQL-выражение
func
Функция
i, j, k
Целочисленные выражения
list
Выражение списка
num
Числовое выражение целого типа или с плавающей точкой
ref
Выражение ссылки на объект
str
Строковое выражение
T
Произвольный тип
Если дано OQL-выражение ехрг, то
(ехрг)
также является OQL-выражением того же типа, что и выражение ехрг. Круглые скобки используются для операций группирования и переопределения приоритета операторов, который определяется в разделе 11.6.
Доступ к объектам
Язык OQL предоставляет доступ к объектам базы данных на том уровне абстракции, на котором они определены в приложении. Доступ возможен также к свойствам объекта, включая атрибуты, связи и операции.
Именованные объекты
Как указывалось в разделе 6.2, в объектной базе данных ODMG с любым объектом можно связать некоторое имя. Оно представляет собой выражение запроса, которое можно использовать для доступа к объекту. Тип именованного объекта определяет тип выражения запроса.
Имя объекта — это действительное законченное выражение запроса. Предположим, что существует объект коллекции с именем Depts, который содержит ссылку на каждый экземпляр класса Department. В языке C++ для связи имени с объектом используется функция-член set_object_name в классе d__Database. Выражение
Depts
является действительным OQL-запросом, который возвращает объект коллекции. Для корректного анализа OQL-запроса необходимо, чтобы имя объекта состояло из одного слова (без встроенных пробелов и операторов). Например, имя
Network_Architecture
можно связать с экземпляром класса Department, представляющим отдел сетевой архитектуры (Network Architecture Department). Эти имена можно использовать как действительные законченные запросы, а также как операнды для других выражений запросов. Тип выражения запроса определяется типом именованного объекта.
Версия 1.2 стандарта ODMG-93 напрямую не поддерживает экстенты, которые представляют собой множество всех экземпляров данного класса. Следовательно, не существует стандартного средства для выполнения запроса на всех экземплярах некоторого типа, как при использовании таблиц в языке SQL. В реализации ODMG-93 можно определить эквивалент экстента путем создания именованной коллекции, со-
держащей ссылку на каждый экземпляр определенного типа. В этом случае, как правило, используется имя класса (или сокращенное имя, например Dept), к которому добавляется окончание s, означающее (в английском языке) множественное число (экземпляров). Затем запрос использует способность именованного объекта в языке OQL ссылаться на коллекцию экземпляров. Версия 2.0 стандарта ODMG уже поддерживает экстенты, и реализации языка OQL позволяют связывать имя с каждым экстентом. Имя можно затем использовать для доступа к экземплярам класса.
Именно приложение решает, какие объекты должны иметь имена. Как показано выше, объект может быть либо одиночным, либо “коллективным”, т.е. коллекцией, содержащей ссылки на множество экземпляров. В примерах следующих глав предполагается, что у нескольких экземпляров класса Employee есть имена:
сео,
president,
highest_paid, smartest.
Компания, возможно, проводит IQ-тестирование своих сотрудников, и служащему с самым высоким коэффициентом присваивается “переходящее звание” самого интеллектуального (smartest).
Если приложение часто получает доступ к определенному набору объектов, можно создать именованную коллекцию, которая будет содержать ссылку на каждый экземпляр этого набора. Приложение может поддерживать несколько именованных коллекций. Набор объектов, подлежащих совместной обработке, задается статически. Такой подход способствует эффективной обработке запросов, поскольку в этом случае не требуется никакого динамического вычисления предикатов.
Доступ к атрибутам и обхоу связей
Дано выражение ref, которое ссылается на экземпляр типа Ti, и атрибут attr класса т,, имеющий тип т2. Тогда два выражения
ref->attr иref.attr
можно использовать для доступа к атрибуту attr, адресуемому ссылкой ref. Оба выражения имеют тип Т2. Здесь можно применить любой из операторов: “стрелка” (->) или “точка" (.), поскольку в OQL они взаимозаменяемы. К ним прибегают при доступе к атрибуту или обходе связей. Атрибутом может быть данное- или функция-член. Если окажется, что выражение ref ссылается на несуществующий объект или содержит null-значение, генерируется исключение.
Язык OQL не обеспечивает соблюдение таких спецификаторов доступа C++, как public, private и protected, и предоставляет прямой доступ ко всем данным-членам класса. К сожалению, такая организация позволяет конечным пользователям непосредственно обращаться к “строительным блокам”, составляющим реализацию класса. Не исключено, что какая-нибудь конкретная реализация языка OQL может обеспечить управление раздельным доступом к членам класса, что позволит ограничить доступ пользователя к атрибутам. Операции в открытом интерфейсе класса, который заключает в себе моделируемую абстракцию, можно сделать доступными для всех.
Выражение
Network_Architecture._budget
обращается к атрибуту _budget экземпляра класса Department с именем Network_Architecture. Тип выражения запроса совпадает с типом атрибута _budget (d_ULong).
Если атрибут ссылается на объект типа т, выражение запроса, использующее этот атрибут, имеет тип ссылки на класс т. Запрос
Network_Archi.tecture->_manager
является выражением пути, которое с помощью члена „manager в классе Department позволяет перейти от экземпляра этого класса к экземпляру класса Employee.
Если атрибут имеет тип т, а класс т содержит собственные вложенные атрибуты, то для получения доступа к вложенному атрибуту можно неоднократно применить операторы “стрелка” (->) и “точка" (.). Например, выражение
Network„Architecture->„manager->„address.city представляет выражение запроса, который обращается к имени города (city), где проживает менеджер („manager), работающий в отделе сетевых архитектур (Network_Architecture). Класс Employee выведен из класса Person, который содержит атрибут „address типа Address. Класс Address содержит член с именем city типа string. Таким образом, предыдущее выражение имеет тип string. В этом запросе используется вложенный атрибут, отношения наследования и операторы навигации.
Разыменование
Предположим, у вас есть выражение ref, которое является ссылкой на объект типа т. Значение выражения ref — объектная ссылка, и ее представление в одной реализации будет отличаться от представления в других, поскольку в каждой из них используются различные подходы к представлению ссылок в базе данных. Допустим, что приложению нужно получить доступ к значениям всех данных-членов, содержащихся в объекте, адресуемом выражением ref. Выражение
*ref
выполняет операцию разыменования. Тип результата этой операции представляет собой объект типа т, а значение результата охватывает значения каждого из атрибутов экземпляра класса т, адресуемого выражением ref. Этот метод эквивалентен использованию оператора разыменования “звездочка” (*) в языке C++, когда ref является указателем или ссылкой (d_Ref<T>).
Выражение запроса, использующее имя именованного объекта, например
Network_Architecture,
в действительности генерирует ссылку на экземпляр класса Department. Представление этой ссылки при использовании языка OQL внутри С++-программы имело бы вид d„Ref<Department>. При интерактивном использовании OQL представление ссылки целиком зависит от конкретной реализации. Чтобы получить доступ ко всем атрибутам нужного отдела, используется операция разыменования.
*Network„Architecture
Результат этого выражения равен значению экземпляра класса Department, которое состоит из значений всех атрибутов.
Ссылки на ппН-объекты
Запрос может иметь выражение, которое использует объектную ссылку с null- значением. Для представления null-значения объектной ссылки служит ключевое слово языка OQL nil. Запрос может попытаться использовать “нулевую” объектную ссылку с помощью операторов . или -> либо оператора разыменования *. Результат такой операции не определен. Для представления неопределенного значения служит ключевое слово языка OQL undefined. Для выяснения, определено ли некоторое выражение, можно использовать булевы операторы, перечисленные в табл. 11.2. Результат использования неопределенного значения в качестве операнда для любого булева оператора сравнения (=, !=, с, <=, >, >=) принимает ложное значение. Любое другое использование неопределенного значения в запросе приводит к генерации исключения.
Таблица 11.2. Операторы, позволяющие узнать, определено ли выражение
Оператор
Значение
is_defined(ехрг)
Значение true, если ехрг определено; в противном случае — false
is_undefined(ехрг)
Значение true, если ехрг не определено; в противном случае — false
Выполнение операций с объектами
Язык OQL позволяет выполнять операции над экземплярами классов. Точнее, вы можете вызвать функцию-член C++, определенную в некотором классе приложения. Пользователям запросов можно предоставить интерфейс, который напрямую свяжет их с абстракциями, определенными в объектной модели. К сожалению, в некоторых OQL-средах такая возможность отсутствует.
При данном выражении ref, которое является ссылкой на экземпляр класса т, и операции орег, определенной как функция-член класса т без аргументов, каждое из этих четырех выражений
ref->operref.орегref->oper()ref.oper()
применяет операцию орег к объекту, адресуемому выражением ref. Круглые скобки необязательны, когда операция не имеет аргументов. Без круглых скобок синтаксис вызова операции идентичен синтаксису доступа к атрибуту. Тип выражения соответствует типу возврата операции орег, а значение возврата выражения — это значение, возвращаемое при вызове операции орег. Если функция имеет тип возврата, соответствующий типу void в языке C++, то значение этого выражения устанавливается равным nil. Если выражение ref ссылается на удаленный или несуществующий объект, генерируется исключение d_Error_RefInvalid.
Если операция орег требует передачи операндов и тип выражений ехрг,. совместим с типами соответствующих операндов, требуемых операцией орег, то следующие выражения
ref->oper(expri, expr2, . . . , exprn)
ref.орег(exprl, expr2, . . . , exprn)
применяют операцию орег к объекту, адресуемому выражением ref, с передачей параметров expri.
Конкретную ссылку можно объявить как ссылку на базовый класс, но она может содержать ссылку на экземпляр производного класса. При вызове операций язык OQL поддерживает полиморфизм. Если операция орег является виртуальной функцией C++, то действительный выбор вызываемой функции основан на типе адресуемого объекта.
G. Приведение типа объектных выражений
При данном выражении ехрг типа Ti и типе т2, где Ti и т2 представляют один и тот же тип или тип т2 выведен из типа Ti, выражение
(Т2) ехрг
утверждает, что ехрг является экземпляром класса т2. Этот метод аналогичен операции dynamic_cast<T2> в языке C++. Он используется главным образом для нисходящего приведения типа (downcasting) объектной ссылки, т.е. при переходе от известного базового класса к производному (возможно, но совсем необязательно). Нисходящее приведение типа при начальном анализе запроса и контроле типов означает, что с
выражением ехрг следует обращаться как с выражением типа т2. Во время выполнения приложения процессор запросов проверяет, действительно ли выражение ехрг является экземпляром класса т2. Если ехрг — экземпляр класса т2, то результат этого выражения представляет собой ссылку на класс т2, которую затем можно использовать для доступа к любому свойству класса т2. Если же окажется, что выражение ехрг не является ни экземпляром класса т2, ни экземпляром, выведенным из класса т2, будет сгенерировано исключение соответствующего типа.
Построение объектов и структур
В запросе можно построить объект, хотя это будет транзитный экземпляр, который нельзя поместить в базу данных. Если даны тип Т с атрибутами attn типа it и выражения ехрг, (где i равно от 1 до п), причем для каждого значения i атрибут attri и выражение ехрг, имеют одинаковый тип Ti, то выражение
Т (attri:expri, attr2:expr2, . attrn:exprn)
определяет новый экземпляр класса т, атрибуты которого attri инициализируются выражениями expri. При этом не все атрибуты класса т обязательны; отсутствующие атрибуты инициализируются действующим по умолчанию нулевым значением. Предоставляемые атрибуты необязательно должны находиться в том порядке, в котором они объявлены в C++. При создании такого объекта конструктор не вызывается. Поэтому пользователь запроса может инициализировать объект значениями, которые могут нарушить ограничения, действующие для данного класса. Мне кажется это решение неудачным. Если приложению нужно создать объект с помощью конструктора, для этого можно определить специальную функцию, а затем сделать ее доступной в среде запросов.
С помощью следующего OQL-выражения создается экземпляр класса Person.
Person (
_fname:"Jerry”,
_lname:"Gaines”
_address: Address (
street:”1038 Western Branch Blvd."
city:"Chesapeake”
state:"VA"
zip_code: ”23309" )*
_phone_number:"8045554309”
)
Внутрь объекта Person встраивается объект типа Address.
В языке OQL можно также построить новую структуру. При данных именах атрибутов attr! и выражениях ехрг2 (где 1 равно от 1 до п) выражение
struct(attri:exprl, attr2:expr2, ..., attrn:exprn)
создает структуру, которая содержит атрибуты attri, инициализированные значениями ехрп. Тип атрибута attn определяется типом выражения ехрп, а именно типом Ti. Выражение
struct(attri:exprl, attr2:expr2, attr3:expr3)
создает структуру в соответствии со следующим С-подобным определением.
struct {
Ti attri;
Т2 attr2;
- Т3 attr3;
} value = { expri, expr2, expr3 };
Эти структуры определяются динамически и не связаны с типом, определенным в языке программирования приложения. Они используются в выражениях запросов для получения многозначного результата. В главе 12 приведены примеры использования такой возможности.
Выражение
struct(title:"Lion King", rating:"G", sales:10000000)
определяет структуру, содержащую характеристики кинофильма. Она включает строковый атрибут title со значением "Lion King", строковый атрибут rating со значением "G" и целочисленный атрибут sales со значением 10 000 000.
Атомарные литеральные выражения
В стандарте ODMG для определения простого доменного типа используется термин атомарный литерал. Язык OQL содержит выражения, которые работают с атомарными литеральными типами.
Примитивные доменные типы
Язык OQL поддерживает следующие примитивные доменные типы:
нулевая объектная ссылка: nil;
булев: true, false;
целый (32 бита);
вещественный, состоящий из мантиссы и необязательной экспоненты, например 3,1416 или 31416е-4;
символ, заключенный в одинарные кавычки, например ' D';
строка (последовательность символов), заключенная в одинарные или двойные кавычки, например "Привет, мир'".
Для совместимости с SQL в OQL разрешается использовать для строк или одинарные, или двойные кавычки ("Привет!" или 'Привет! '). Однако если в одинарные кавычки заключается односимвольная строка, возникает неопределенность. Например, при использовании ' D' неясно, выражение какого типа имеется в виду — строка или символ. Для разрешения этой проблемы в запросе применяется контекст выражения. Чтобы избежать путаницы, следует использовать для строк двойные кавычки.
Арифметические выражения
Для целых чисел и чисел с плавающей точкой определены унарные арифметические операторы. Они перечислены в табл. 11.3, где num — это выражение целого или вещественного типа. Тип выражения соответствует типу операнда num.
Бинарные арифметические операторы для целых чисел и чисел с плавающей точкой приведены в табл. 11.4. Выражение будет иметь вещественный тип, если хотя бы один из операндов (numi или numj) является вещественным. Но если оба операнда целые, результат будет иметь целый тип. Оператор mod не поддерживает выражений вещественного типа.'г
Таблица 11.3. Унарные арифметические операторы
Оператор
Описание■"
+ num
Значение num
- num
Отрицание num
abs num
Абсолютное значение num
Таблица 11.4. Бинарные арифметические операторы
Оператор
Описание
numi + numj
Сложение
numi - numj
Вычитание
numi * numj
Умножение
numi / numj
Деление
numi mod numj
Сложение по модулю
Операторы сравнения
Бинарные операторы отношений (табл. 11.5) возвращают булево значение (истинное или ложное). Эти операторы можно использовать со всеми примитивными доменными типами: числовыми типами (int, float), символами, строками и значениями булева типа (false<true). Выражения операндов (expri или exprj должны иметь одинаковый или совместимый тип (целый и вещественный считаются совместимыми).
Например, следующий запрос определяет, больше ли оклад (salary) директора- распорядителя компании (chief executive officer — СЕО) или ее президента (president).
ceo->salary() > president->salary()
Таблица 11.5. Бинарные операторы сравнения
Оператор
Описание
ехргг = exprj
Истинное значение, если ехрг^ равно exprj, в противном случае — ложное
expr, ! = exprj
Истинное значение, если ехргх не равно exprjt в противном случае — ложное
expri < exprj
Истинное значение, если expri меньше exprj, в противном случае — ложное
expri <= expr-j
Истинное значение, если expri меньше или равно exprj, в противном случае — ложное
expri > exprj
Истинное значение, если expri больше exprj, в противном случае — ложное
expri >- exprj
Истинное значение, если expri больше или равно exprj, в противном случае — ложное
Булевы операторы
Унарные и бинарные булевы операторы, поддерживаемые в OQL, перечислены в табл. 11.6 в порядке убывания приоритета. При этом как операнды, так и результат операции имеет булев тип. Оператор not имеет более высокий приоритет, чем оператор and, приоритет которого выше, чем у оператора or. В разделе 11.6 описан приоритет всех операторов запросов. При данных выражениях а, b и с следующие выражения эквивалентны.
not a and b or сэквивалентно( (not a) and b) or с
a or not b and сэквивалентноa or ( (not b) and c)
Круглые скобки можно использовать для переопределения влияния приоритета операторов. Кроме того, они могут улучшить читабельность сложного булева выражения.
Таблица 11.6. Булевы операторы
Оператор
Описание
not bool
Истинное значение, если bool ложно, и ложное, если bool истинно
booli and boolj
Истинное значение, если boolx и boolj истинны, в противном случае — ложное
booli or boolj
Истинное значение, если хотя бы один из операндов booli или boolj истинен, в противном случае — ложное
Оптимизатор запросов может выбрать порядок, в котором эти выражения будут вычисляться в пределах ограничений, устанавливаемых приоритетом операторов, и в пределах групп, обозначенных круглыми скобками. Если оптимизатор предполагает, что один из операндов можно вычислить более эффективно, он переупорядочит вычисление ради повышения производительности. При вычислении and- или ог- выражения первым можно вычислить левый или правый операнд. Вычисление булева выражения можно сразу же остановить, если на каком-то этапе будет известен результат всего выражения. Если при вычислении and-выражения первый операнд окажется ложным, то и весь результат будет иметь значение false, поэтому нет смысла вычислять другой операнд. Если при вычислении or-выражения первый операнд окажется истинным, то и результат будет иметь значение true, и нет смысла вычислять другой операнд.
Строковые выражения
Язык OQL позволяет операции со строками, перечисленные в табл. 11.7. Здесь используются следующие обозначения:
str, stri, str-j — выражения типа string;
char — выражение символьного типа;
тип — выражения целого типа.
Начальный индекс строки имеет значение 0. Синтаксис некоторых из этих выражений идентичен синтаксису индексированных коллекций, определенных в табл. 11.14.
Таблица 11.7. Строковые выражения
Выражение
Тип результата
Значение результата
stri | | strj
Строка
Конкатенация stri и strj
strl + Strj
Строка
Конкатенация stri и strj
char in str
Булев
Истинное значение, если char содержится в str, в противном случае — ложное
str[n]
Символ
п+1-й символ в строке str
str[m:n]
Строка
Подстрока строки str отт+1-го символа до п+1-го символа (т<=п)
str like strj
Булев
Истинное значение, если строка в str совпадает с комбинацией символов (разъяснение ниже), определенной в strj
Чтобы создать одну строку, которая содержит имя директора-распорядителя (СЕО) компании, можно использовать любое из следующих выражений.
ceo->_first_name II ' ' | | ceo->_last__name
ceo->_first_name + ' ' + ceo->_last_name
Выражение
'O' in "Object Database"
истинно, в то время как выражение
'О' in "Relational Database" ■
ложно.
При данном строковом выражении str, которое содержит значение "object databases offer lots of advantages", следующее выражение будет равно символу ' d'.
str [7]
Следующее выражение будет равно строке "data".
str[7:10]
Оператор like возвращает истинное значение, если в строке существует заданная комбинация символов. Эта комбинация должна быть строковым литералом, который может включать шаблонные символы, приведенные в табл. 11.8. С помощью обратной косой черты (\), размещенной перед одним из этих символов, можно отменить его использование в качестве шаблонного, если он должен находиться в самой строке. Этот механизм символьных последовательностей был введен в версии 2.0 стандарта ODMG.
Таблица 11.8. Шаблонные символы
Шаблонные символы
Интерпретация
? или _
Совпадает с любым символом
* или %
Любая подстрока, включая пустую подстроку
Если дана строка str, определенная выше, следующее выражение возвращает истинное значение.
str like "object_data*advantage?"
Если строка str содержит значение "What % of the market?", то выражение истинно.
str like "*\%*market\?"
Коллекции
Язык OQL поддерживает коллекции элементов произвольного типа. Коллекции и соответствующие им имена в интерфейсе C++ приведены в табл. 11.9. В версию 2.0 стандарта ODMG добавлена коллекция словаря. В данном разделе описаны операции, которые можно применять к экземплярам этих коллекций. Коллекция может быть автономным перманентным объектом, встроенным в качестве члена класса или созданным как литерал коллекции в OQL-выражении. Если коллекция встроена как член класса, она не имеет идентификатора. В OQL-спецификации такая коллекция называется литералом, или значением коллекции. Автономные перманентные коллекции называют объектами коллекций.
Таблица 11.9. OQL-коллекции и их С++-эквиваленты
OQL-коллекция
С++-имя
Характеристики элементов
set
d_Set<T>
He упорядочены, без дубликатов
bag
d_Bag<T>
Не упорядочены, дубликаты разрешены
list
d_List<T>
Упорядочены, дубликаты разрешены
array
d_Varray<T>
Упорядочены, дубликаты разрешены
dictionary
d_Dictionary<K, V>
Не упорядочены, без дубликатов
Создание литералов коллекций
Литерал коллекции можно создать с помощью запроса. В этом случае он будет транзитным, и его нельзя поместить в базу данных. Литерал также не подлежит объектной идентификации. В языке OQL можно создать литералы коллекций множества, мультимножества, списка и массива. В версии 2.0 стандарта ODMG для создания литерала словаря синтаксис не определен.
set
При выражениях expri, ехрг2, -.., ехргп типа т выражение
set(expri, ехрг2, .... ехргп)
представляет множество, содержащее элементы, определенные выражениями expri.
hag
При выражениях expri, ехрг2, -.., ехргп типа т выражение bag(expri, ехрг2, . .., ехргп)
определяет мультимножество, содержащее элементы, определенные выражениями expri-
list
При данных выражениях expri, expr2, .... exprn типа т два выражения list(expri, ехрг2, •••, ехргп) (expri, ехрг2, . . . , ехргп)
определяют список, содержащий элементы, определенные выражениями expri.
Если выражения min и max имеют целочисленный или символьный тип и при этом min < max, то два выражения
list(min . . max) и(min . . max)
определяют список, содержащий значения'
list(min, min+1, ..., max-1, max).
Например, ниже показаны пары выражений, причем выражения слева представляют собой сокращенную форму записи выражений, расположенных справа.
list (5, .. 9)(1, ■ ■ 3)list ( 'а', .. ' е ' )list (5, .. 9)(1, ■ ■ 3)list ( 'а', .. ' е ' )list list listlist list list(5, 6, 7, 8, 9)
list (5, .. 9)
(1, ■ ■ 3)
list ( 'а', .. ' е ' )
list (5, .. 9)
(1, ■ ■ 3)
list ( 'а', .. ' е ' )
list list list
list list list
(1, 2, 3)
( ' а ' , 'b ' , ' с.' , ' d' , ' е' )
array
При выражениях expri, expr2, . . ., exprn типа T выражение array(expri, ехрг2, . . ., ехрг;1)
создает массив, содержащий элементы, определенные выражениями ехрп.
Операции, выполняемые над коллекциями
Существуют операции, которые можно применить к выражениям, включающим все коллекции. Некоторые из них связаны с кардинальным числом коллекции, т.е. количеством элементов в коллекции. Коллекция, не содержащая элементов, называется пустым, или нулевым множеством. Коллекция, состоящая только из одного элемента, именуется одноэлементной (singleton). В списке поддерживаемых операций (табл. 11.10) элемент coll представляет выражение коллекции, содержащее элементы ехрг типа т. Операция element генерирует исключение, если операнд не является одноэлементной коллекцией.
С помощью следующего запроса можно определить, работает ли самый интеллектуальный служащий компании в отделе сетевых архитектур.
smartest in Network_Architecture.employees
(Напомню, что в этом запросе используется два именованных объекта.)
В следующих двух запросах используется операция count.
count(Depts)
count(Network_Architecture.employees)
Первый запрос сообщает количество отделов, а второй возвращает количество служащих в отделе сетевых архитектур.
Результат применения операции distinct к коллекции set<T> или Ьад<Т> имеет тип set<T>. Поскольку множество set<T> не содержит дубликатов, в результате применения операции distinct к коллекций set<T> остается прежнее содержимое множества. Если операция distinct применяется к такой упорядоченной коллекции, как list<T> или аггау<т>, то тип результата совпадает с типом операнда. В этом случае упорядочение элементов в операнде list<T> или аггау<Т> сохраняется, по-
вторяющиеся элементы удаляются, а результат содержит первое вхождение каждого значения в операнде.
Например, результат выражения
distinctf list(1,2,3,4,5,4,3,2,1) )
принимает следующий вид.■
list(1,2,3,4,5)
Таблица 11,10. Операции, применяемые к коллекциям
Выражение
Тип результата
Значение результата
exists(coll)
Булев
Истинное, если coll содержит хоть один элемент
unique(coll)
Булев
Истинное, если coll содержит только один элемент
element(coll)
Экземпляр класса т
Единственный элемент ехрг в coll, если коллекция coll одноэлементная
expr in coll
Булев
Истинное, если ехрг является элементом в coll
distinct(coll)
См. ниже
Коллекция с удаленными повторяющимися элементами коллекции coll
count(coll)
Целый
Количество элементов в коллекции coll
Совокупные операторы
Совокупные операторы можно применять к коллекции, которая содержит элементы числового типа (целого или вещественного). Если предположить, что coll — это выражение коллекции, которая содержит элементы числового типа, в языке OQL поддерживаются операции, перечисленные в табл. 11.11.
Таблица 11.11. Совокупные операторы
Выражение
Тип результата
Значение результата
min(coll)
Экземпляр класса т
Минимальное значение в coll
max(coll)
Экземпляр класса т
Максимальное значение в coll
sum(coll)
Экземпляр класса т
Сумма всех элементов в coll
avg(coll)
Вещественный
Вычисляемое среднее всех значений в coll
Операции, применяемые к множествам и мультимножествам
В этом разделе рассматриваются операции, применяемые исключительно к множествам и мультимножествам.
Бшриые операции нау множествами
Язык OQL поддерживает такие операции над множествами, как объединение, пересечение и вычитание, формальное определение которых приведено в разделе 7.4.2. При выражениях colli и со112, которые являются экземплярами либо множества (set<T>), либо мультимножества (bag<T>), можно выполнить операции, перечисленные в табл. 11.12.
Таблица 11.12. Операции над множествами и мультимножествами
Операция
Описание
colli union coll.
Объединение colli и col 12"
colli intersect coll2
Пересечение colli и coll2
colli except coll.
Разность множеств, colli - coll2
Если одна из коллекций является мультимножеством (bag), а другая — множеством (set), то перед применением операции множество логически преобразуется в мультимножество. Если оба операнда colli и со112 имеют тип set<T>, результат такой операции также будет иметь тип set<T>. Если любой из операндов (colli или со112) — мультимножество, результат также будет мультимножеством. Пересечением двух мультимножеств является мультимножество, которое содержит минимальное количество повторяющихся значений из соответствующих их наборов в каждом из мультимножеств.
При вычислении следующих выражений генерируется результат, показанный справа.
set(l,2,3) union set (3,4,5)set(1,2,3,4,5)
set(l,2,3) union bag(3,4,5)bag(1,2,3,3,4,5)
bag(1,2,2,3,4) except bag(l,2,3,4)bag(2)
bag(1,2,2,3,4,4) intersect bag(1,1,2,4,4,4)bag(l,2,4,4)
Операции пи определению отношеиий подмиожества и супермнижестеа
При использовании выражений colli и со112, которые означают множества или мультимножества, можно определить, является ли одно из них подмножеством или супермножеством другого. Формальное определение этих операций приведено в разделе 7.4.1. Если один из операндов (colli или со112) является мультимножеством, то другой перед выполнением операции логически преобразуется в мультимножество. Выражения, приведенные в табл. 11.13, при вычислении генерируют булев результат, означающий, что это выражение является истинным или ложным. Коллекция colli является собственным подмножеством коллекции со112 только в том случае, если со112 содержит все элементы из коллекции colli и коллекция colli не равна коллекции со112, т.е. со112 имеет некоторые дополнительные элементы. Слово собственное подразумевает, что коллекции не равны.
Таблица 11.13. Операции по определению отношений подмножества и супермножества,
применяемые к множествам и мультимножествам
Выражение
Описание
colli < colli
Истинное значение, если colli является собственным подмножеством со112
colli <= colli
Истинное значение, если colli — подмножество со112
colli > C0II2
Истинное значение, если colli является собственным подмножеством со112
colli >= C0II2
Истинное значение, если colli — подмножество со112
Следующие выражения генерируют результат булева типа, приведенный справа. set(l,2)<set(l,2)false
set(l,2)<set(l,2,3)true
set(l,2)<=set(l,2)true
bag(1,2,3)>bag(1,2)true
bag(l,2,3)>bag(5,6)false
Операции, применяемые к индексирпванным коллекциям [спискам и массивам]
Ряд операций можно применить к индексированным коллекциям list и array. Первый элемент индексированной коллекции имеет индекс, равный 0.
Предположим, что выражения colli, coll2 и со113 означают индексированные коллекции (list<T> или аггау<Т>), содержащие элементы типа т. Допустим также, что i и j — выражения целого типа. Оператор конкатенации + можно использовать только для коллекций одинакового типа, т.е. обе коллекции colli и со112 должны быть или массивами, или списками. Операции, выполняемые над индексированными коллекциями, приведены в табл. 11.14. Синтаксис и семантика некоторых из этих операций аналогичны синтаксису и семантике операций со строками, определенных выше (см. табл. 11.7). В табл. 11.15 приведены примеры этих выражений и их результаты.
Таблица 11.14. Операции над индексированными коллекциями
Выражение
Тил результата
Значение результата
first (colli)
Экземпляр класса т
Первый элемент colli
last(colli)
Экземпляр класса т
Последний элемент colli
colli [i]
Экземпляр класса т
Элемент коллекции colli в индексной позицииi
colli [i:j]
colli
Подколлекция коллекции colli, начинающаяся элементом с индексом i и оканчивающаяся элементом с индексом j, где i<j
colli + coll2
СО113
Конкатенация коллекций colli и со112
Таблица 11.15. Примеры выполнения операций над индексированными коллекциями
Выражение запроса
Значение результата
first(list(1,2,3))
1
last (list(1,2,
3) )
3
list(1,3,5) [1]
3
array(1,3,5,7,
11)[1:3]
array(3,5,7)
array(2,4,6) +
array(3,5,7)
array(2,4,6,3,5,7)
Операции, нрименяемые к слпварям
Если дано выражение коллекции diet с типом ключа Ti и типом значения т2, а также выражение ехрг типа Ti, то выражение,
diet[ехрг]
является выражением типа т2, значение которого связано со значением выражения ехрг в словаре diet.
Преобразования коллекций
Существуют операции, предназначенные для преобразования коллекции из одного типа в другой. Они могут быть полезны, если в соответствии со схемой базы данных некоторая коллекция имеет один тип, а запрос требует другой. Кроме того, такие операции используются для преобразования результата выражения запроса из одного типа коллекции в другой.
Преобразования межуд объектами и литералами коллекций
Можно выполнить преобразование объекта коллекции в литерал коллекции и обратно. Такое преобразование необходимо при сравнении объектов коллекций, хранимых в базе данных, с литералами, созданными в OQL-запросе. ■
При выражении colli, представляющем автономный перманентный объект коллекции с идентификатором, выражение
*со111
использует операцию разыменования для преобразования объекта коллекции в литеральное значение коллекции, эквивалентное литералам коллекций, описанным в разделе 11.3.1. Это выражение разыменования требуется при определении равенства между объектом коллекции и литералом коллекции (подробности в разделе 11.4).
Можно также преобразовать литерал в объект. Если т — это имя типа коллекции, определенной в приложении, а со112 — литерал коллекции, совместимый с т, то выражение
Т(со112)
создает объект коллекции из литерала коллекции. В языке OQL не поддерживается синтаксис для шаблонов, как в языке C++, поэтому следующее выражение недействительно для OQL.
d_Set<int>( set(l,2,3) )
Чтобы в языке OQL создать объект коллекции типа d_Set<int>, необходимо определить следующий тип с помощью С++-операции typedef.
typedef d_Set<int> SetOfIntegers;
Затем, чтобы создать объект коллекции типа SetOfIntegers, который в точности соответствует типу d_Set<int>, тип SetOfIntegers вполне можно использовать в следующем OQL-выражении.
SetOfIntegers( set (1,2,3) )
С помощью этого выражения литерал коллекции был преобразован в объект коллекции.'
Преобразование сииска в множество
В табл. 11.10 показано, что операцию distinct можно использовать для преобразования мультимножества (bag) в множество (set). При данном выражении списка list, содержащем элементы типа т, выражение1,
listtoset(list)
создает множество set, состоящее из элементов списка list, из которого удалены повторяющиеся элементов.
Преобразование шпекции в список
Любую коллекцию можно преобразовать в список. Но список является упорядоченной коллекцией, поэтому при таком преобразовании необходимо определить порядок следования элементов коллекции. Для преобразования коллекции в список можно воспользоваться директивой order by, которая описана в разделе 12.5.
Выравнивание коппекции
При данном выражении коллекции coll, содержащей элементы, которые сами являются коллекциями некоторого типа т (т.е. коллекция coll имеет тип со111<со112<Т>>), выражение
flatten(coll)
преобразует коллекцию coll в коллекцию элементов типа т. Эта операция выравнивания действует только на глубину в один уровень, даже если элементы коллекции являются вложенными на большее число уровней. (Но при необходимости для более глубоко вложенных коллекций можно использовать вложенные операции flatten.) Коллекции coll; и со112 могут иметь один из известных типов коллекций (set, bag, list или array). Тип результата и значение выражения flatten (coll) зависят от конкретного типа коллекций colli и со112. В табл. 11.16 представлены тип и значение результата для каждого возможного сочетания пар коллекций. В первом и втором столбцах указаны типы коллекций colli и со112 соответственно. Элемент coll: в первом столбце таблицы означает, что вместо него можно подставить любой тип коллекции. Например, первая строка таблицы подразумевает, что если коллекция со112 является множеством (set), то результат будет иметь тип set, независимо от типа коллекции colli. Таблица составлена в предположении, что мы имеем дело с таким выражением coll, тип которого представлен в виде colli<coll2<T>>. Если результат операции flatten имеет тип set, повторяющиеся значения будут удалены, поскольку множество не содержит дубликатов.
Таблица 11.16. Тип и значение результата оператора flatten
colli
coll2
Тил результата
Значение результата операции flatten(collKcoll2<T»)
colli
set
set<T>
Объединение всех со112<т> в colli
colli
bag
bag<T>
Объединение всех со112<т> в colli
list
list
list<T>
Конкатенация всех со112<т> на основе их порядка в colli
array
list
list<T>
Конкатенация всех со112<т> на основе их порядка в colli
array
array
array<T>
Конкатенация всех со112<т> на основе их порядка в colli
list
array
array<T>
Конкатенация всех со112<т> на основе их порядка в colli
set
list или array
set<T>
Каждый элемент со112<т> преобразуется в set<T>, и выполняется объединение этих множеств
bag
list или array
bag<T>
Каждый элемент со112<Т> преобразуется в bag<T>, и выполняется объединение этих мультимножеств
Рассмотрим несколько примеров выполнения операции flatten. Предположим, что следующие коллекции имеют заданные элементы, приведенные в табл. 11.17.
Таблица 11.17. Исходные данные (коллекции и их элементы) для примеров операции flatten
Коллекция
ЭлементыКоллекция Элементы
set:
1234bagl121
set2
2468bag2245
set3
1357bag3355
listl
123arrayl2468
list2
246array21224
listl
13 3
Результаты выполнения операции flatten представлены в табл. 11.18.
Таблица 11.18. Примеры использования оператора flatten
Выражение выравнивания
Результат
flatten(list(seti, set2, set3) )
set(1,2,3,4,5,6,7,8)
flatten(bag(setl,set2,set3))
set(1,2,3,4,5,6,7,8)
flatten(set(bagl,bag2,bag3))
bag(1,1,2,2,4,5,5,5,3)
flatten(array(bagl,bag2,bag3))
bag(1,1,2,2,4,5,5,5,3)
flatten(array(listl,list2))
list (1,2,3,2,4,6 )
flatten(list(listl,list2,list3))
list(1,2,3,2,4,6,1,3,3)
flatten(set(listl,Iist2,list3))
set(1,2,3,4,6)
flatten(list(arrayl,array2))
array(2,4,6,8,1,2,2,4)
flatten(set(arrayl,array2))
set(1,2,4,6,8)
flatten(bag(arrayl, array2))
bag(2,2,2,1,4,4,6,8)
Равенство
В языке OQL поддерживаются операции равенства и неравенства выражений, которые либо являются объектными ссылками, либо значениями произвольного типа. Для выражений refi и ref2, которые представляют собой ссылки на объекты совместимых типов, и выражений expri и ехрг2, выступающих в роли значений совместимых типов, в табл. 11.19 приведены булевы выражения равенства и неравенства, поддерживаемые языком OQL.
Выражения expri и ехрг2 совместимого примитивного доменного типа равны, если равны их значения. Если expri и ехрг2 представляют собой выражения запросов, означающие структуры одинаковой формы с соответствующими членами одинакового типа, то они будут равны, если равны соответствующие члены этих двух структур. Если существует дальнейшее вложение структур, эта операция применяется рекурсивно.
Таблица 11.19. Операторы равенства и неравенства для ссылок на объекты и значений объектов
Выражение
Значение
refi = ref2
Истинное, если refi и ref2 ссылаются на один и тот же объект
refi ! = ref2
Истинное, если ref! и ref2 не ссылаются на один и тот же объект
expri = expr2
Истинное, если ехрг. и ехрг2 равны
expri I = expr2
Истинное, если ехрг2 и ехрг? не равны
Аналогично равенство двух выражений, означающих значения объектов, определяется путем равенства соответствующих данных-членов объектов. При этом подразумевается, что сравниваются их значения реализации, а не значения абстракции. В языке OQL не используются операторы, которые могут быть определены для типа т в C++. Однако реализация OQL может позволить вызов соответствующих методов и функций. Для этой цели в приложении можно определить операцию равенства. С такой “ролью” вполне могла бы справиться функция-член или независимая функция — главное, чтобы она была доступна в среде OQL. В этом случае пользователю запросов придется вызывать не оператор равенства, а специальную функцию. Тогда операция равенства будет основана не на значении реализации, а на значении абстракции объекта.
Следующий запрос определяет, является ли самый высокооплачиваемый служащий самым интеллектуальным.
highest_paid = smartest
Здесь применяются именованные объекты. В запросе используются имена для получения ссылки на объекты, а затем сравниваются объектные ссылки. Реальные объекты в проверке не участвуют.
Чтобы проверить, имеет ли объектная ссылка null-значение, нужно использовать ключевое слово nil. Следовательно, выражение
ref != nil
можно применять к любому булеву выражению для определения, является ли данная ссылка нулевой. Любая попытка разыменовать ссылку, которая имеет значение nil, сгенерирует исключение d_Error_RefNull.
При данном выражении ref, которое ссылается на объект типа т, выражение *ref разыменовывает эту ссылку, и результатом операции разыменования является значение адресуемого экземпляра класса т.
Следовательно, выражение
*refl = *ref2
проверяет равенство адресуемых объектов. Выражение
*ref = ехрг
определяет, равно ли значение, адресуемое ссылкой ref, значению объекта ехрг.
Автономный перманентный объект коллекции имеет объектный идентификатор. Коллекция, которая встроена в качестве атрибута перманентного объекта, не имеет идентификатора и обрабатывается в среде OQL как литеральное значение коллекции. При выражениях colli и collz, обозначающих независимые перманентные объекты коллекций, выражение
colli = со112
проверяет равенство идентификаторов, что аналогично сравнению двух ссылок. Чтобы протестировать равенство значений коллекций, которое основано на равенстве элементов, необходимо использовать следующее выражение.
*colll = *со112
Но если бы выражения коллекций colli и со112 были встроенными атрибутами (и, таким образом, не имели объектных идентификаторов), выражение
colli = СО112
определяло бы равенство значений коллекций. Встроенные коллекции обрабатываются аналогично литералам коллекций.
Если есть выражение объекта коллекции colli и выражение литерального значения коллекции со112, проверку равенства значений этих выражений следует выполнить в виде следующего выражения.
*со111 = со112
Предположим, что выражение объекта коллекции coll содержит целые числа 1, 2 и 3. Чтобы узнать, является ли коллекция coll множеством, содержащим целые 1, 2 и 3, нужно разыменовать выражение coll и сравнить его с литералом коллекции.
*coll = set(1,2,3)
При двух выражениях значений коллекций, которые являются множествами элементов типа т, эти множества будут равны, если содержат одинаковые значения элементов. Две словарные коллекции равны, если имеют одинаковые ассоциативные пары ключ/значение. Два мультимножества равны, если содержат одинаковые значения элементов с одинаковым количеством вхождений соответствующих элементов. Два списка или два массива равны, если они имеют одинаковые значения элементов, расположенные в одном и том же порядке. Эти правила равенства коллекций совпадают с правилами, определенными для аналогичных коллекций в языке C++.
Функции
Если дана функция с именем func и типом возврата т, которая принимает нулевое или ненулевое количество аргументов типа Ti, и даны выражения ехрп типа Ti или типа, совместимого с типом тч, то выражения
func ()илиfuncfexpri, ехрг2, . .. , ехргп)
являются выражениями типа т, которые вызывают функцию func. Эта функция вызывается средствами языка OQL и возвращает значение типа т. Тип выражений ехрп зависит от типа аргументов, объявленных для функции func. Если функция func не возвращает никакого значения, т.е. имеет С++-тип возврата void, то функция возвратит значение nil. OQL не диктует, на каком языке должна быть написана функция func. Однако среда OQL должна иметь возможность вызвать эту функцию, передать ей аргументы и возвратить значение.
Способность языка OQL вызывать функции или выполнять операции над объектами (см. раздел 11.1.5) делает его весьма расширяемым языком запросов. В рамках этого языка можно обеспечить действия, специфичные для конкретной объектной модели и сферы применения. Значения возврата и аргументов могут быть одиночными скалярными значениями (например, целыми, строками или объектными ссылками) или коллекциями (например, множествами ссылок на экземпляры некоторого класса т). Приложение может определить новые совокупные операции, аналогичные min, max и avg. Например, если приложение требует вычисления среднего значения от некоторого множества целых, можно написать на языке C++ нужную функцию, а затем скомпоновать ее в среде OQL. Кроме того, необходимо написать функцию, предоставляющую объект начальной “точки входа”, который можно было бы использовать в качестве стартовой позиции для навигации в запросе.
Приоритет операторов
В табл. 11.20 перечислены операторы языка OQL в порядке убывания приоритета.
Таблица 11.20. Приоритет операторов языка OQL
Операторы
О
[]
not
- (унарный)
+ (унарный)
in
/
modintersect
+
-
unionexceptI I
<
>
<=>=<some <any <all
! =
like
and
exists
for all'
or
(идентификатор) order by having
group by
where
from
select
Некоторые из этих операторов описываются в главе 12. Строки таблицы упорядочены в порядке убывания приоритета (от высшего к низшему). Операторы, расположенные в одной строке, имеют одинаковый приоритет и группируются слева направо. Операторы <some, <апу и <а!1 называются составными предикатами и описываются в разделе 12.2.3. Для других операторов сравнения (=, !=, <=, > и >=) предусмотрены соответствующие операторы-предикаты. Они имеют тот же приоритет, что и операторы, приведенные в таблице (<some и другие), и группируются слева направо в порядке, указанном в таблице.
Глава 12
Предикаты и запросы коллекций
В главе 11 вы познакомились с большинством базовых выражений запросов и операторов языка OQL. Их можно использовать для построения более сложных выражений запросов. В этой главе рассматриваются предикаты и описываются способы их использования в фильтрации элементов коллекции.
Тем, кто уже работал в среде SQL, знакома конструкция select from where. В OQL предусмотрена аналогичная директива select, но она имеет усовершенствования, которые значительно расширяют возможности языка SQL.
Переменные итераторов
Чтобы опросить элементы коллекции, в выражении запроса используется переменная итератора, предназначенная для ссылки на текущий элемент процесса итерации. Эта переменная объявляется в директиве from запроса select или внутри предиката навешивания квантора общности или квантора существования (подробности ниже). Тип переменной итератора совпадает с типом элементов коллекции, с которым он связывается. Переменные итераторов используются во многих выражениях запросов, описанных в этой главе.
Предикаты
Предикат — это булево выражение, которое отбирает элементы в результате выполнения другого выражения запроса. Оно используется при фильтрации элементов, для которых этот предикат не истинен. Директива where в языках SQL и OQL содержит булев предикат, который рассматривается в разделе 12.3. Для ссылки на текущий элемент внутри предиката используется переменная итератора.
Для составления булевых выражений произвольной сложности можно использовать булевы операторы and, or и not, которые рассматриваются в разделе 11.2.4. Для группирования связанных булевых выражений, переопределения приоритета операторов и упрощения выражений можно использовать круглые скобки.
Навешивание квантора общности
Навешивание квантора общности (universal quantification) — это булево выражение, которое истинно только в том случае, если предоставляемый предикат истинен для всех элементов коллекции. Если предикат ложен для одного или нескольких элементов, выражение квантора общности ложно.
Для переменной var, выражения коллекции coll и выражения предиката predicate булево OQL-выражение
for all var in coll : predicate
истинно только в том случае, если predicate истинен для всех элементов коллекции coll. В противном случае оно ложно. Например, следующий запрос определяет, все ли служащие отдела сетевых архитектур получают жалованье, превышающее $50 000.
for all е in Network_Architecture->employees : e->_salary > 50000
В этом выражении запроса е — переменная итератора, используемая в предикате для ссылки на текущий элемент. Поскольку член employees в классе Department имеет тип d_Set<d_Ref<Employee>>, переменная е имеет тип d_Ref<Employee>. Вычисление этого выражения может сразу же завершиться возвратом значения false, как только будет найден служащий с зарплатой $50 000 или меньше.
Навешивание квантора существввания
Навешивание квантора существования (existential quantification) — это булево выражение, которое истинно, если хотя бы один элемент коллекции истинен для данного предиката. Другими словами, оно истинно, если в коллекции существует элемент, для которого предикат истинен.
При данной переменной итератора var, выражении коллекции coll и булевом выражении предиката predicate булево выражение
exists var in coll : predicate
истинно, если существует хотя бы один элемент коллекции coll, для которого предикат predicate истинен. Если предикат ложен для всех элементов, выражение квантора существования ложно. Например, следующий запрос определяет, существует ли в компании хотя бы один отдел, в котором работает более 250 служащих.
exists d in Depts : count(d.employees) > 250
Реализация этого выражения не нуждается в доступе ко всем элементам коллекции. Она может остановить обработку элементов и возвратить значение true, как только встретит первый элемент, для которого данный предикат истинен. Удачное размещение индексов способствует более эффективному вычислению запроса путем уменьшения количества обрабатываемых элементов.
Составные предикаты
Язык OQL предоставляет синтаксическую вариацию навешивания квантора общности и существования, в которой используются операторы сравнения (=, !=, <, <=, > и >=). В табл. 12.1 перечислены булевы составные предикаты, которые поддерживаются в OQL; здесь выражение coll означает коллекцию элементов типа Т, выражение ехрг — значение типа т, а оператор сравнения ® принадлежит множеству (=, ! =, <, <=, > и >=). Предикаты some и any являются синонимами и имеют эквивалентное поведение. Следующие составные предикаты истинны.
166.'■ Часть II. Язык объектных запросов j
20 = some list(10, 20, 30, 40)
100 < some 1i s t(2, 30 0)
"blue" = any array("red", "blue", "green"
100 > all set (25, 50, 75)
Таблица 12.1. Составные предикаты
Составной предикат
Эквивалентное выражение
expr ® some coll
exists var in coll : expr ® var
expr ® any coll
exists var in coll : expr ® var
expr ® all coll
for all var in coll : expr ® var
Следующие составные предикаты ложны.
20 = some list(l, 2, 3, 4)
100 > some list(2, 45)
5 ! = all bag(3, 1, 5)
select... from... where...
Если даны выражения colli, означающие коллекции, которые содержат элементы типа Ti, переменные итераторов Vi, булев предикат predicate, выражение упорядочения ord, и проекция projection, то приведенное ниже выражение является выражением запроса.
select [distinct] projection
fromcolli as Vi, colli as V2, . .. , colln as Vn
[ wherepredicate ]
[ order by ord]
Квадратные скобки указывают на необязательные ключевые слова или директивы. В выражении запроса могут использоваться также директивы group by и having, которые описываются в разделе 12.4.
Результат выражения запроса select представляет собой коллекцию элементов, тип которых определяется проекцией projection. Тип самой коллекции определяется формой запроса и присутствием (или отсутствием) ключевого слова distinct или директивы order by (табл. 12.2). Использование ключевого слова distinct означает удаление повторяющихся значений. Если же ключевое слово distinct не задано, запрос может иметь (или не иметь) дубликаты. Это зависит от того, есть ли повторяющиеся значения в базе данных и результате запроса.
Таблица 12.2. Тип результата коллекции для запроса select
order by
distinct
Коллекция результата
Удаление дубликатов
✓
✓
list
да
✓
list
нет
✓
set
да
bag
нет
При вычислении выражения запроса select выполняются следующие действия.
Формируется декартово произведение коллекций colli, заданных в директиве from. Логически каждая коллекция обрабатывается как мультимножество (bag) значений типа Ti (bag<Ti>), даже если какая-то коллекция является множеством (set), списком (list) или массивом (array). Результатом декартова произведения является мультимножество (bag) следующего типа.
bag< struct(VI:Т1, V2:T2, ... , Vn:Tn) >
Элементы результата представляют все возможные комбинации значений из каждой коллекции colli.
Декартово произведение фильтруется путем удаления из него элементов, для которых булев предикат predicate в директиве where возвращает значение false. Если директива where опущена, ни один элемент из результата не удаляется. Отсутствие директивы where эквивалентно использованию булева предиката, который принимает значение true для каждого элемента декартова произведения.
Если в выражении запроса присутствует директива order by, результат, полученный на этапе 2, трансформируется в список, который упорядочивается на основе выражения упорядочения ord. Директива order by рассматривается в разделе 12.5.
К результату, полученному на предыдущем этапе, применяется проекция projection.
При наличии ключевого слова distinct из результата удаляются повторяющиеся значения проекции.
Если вы незнакомы с декартовым произведением, рассмотрите следующий пример. Предположим, используются следующие три коллекции.
set< d_Ref<T> >Ci=(objidi,objid2,objid3)
list<d_String>C2=("red","blue","green")
array<int>C3=(7, 11)
Ниже приводятся элементы декартова произведения, образованные выражениями
Ci, С2 И С3.
objidi
"red"
7
objidi
"red"
11
objidi
"blue"
7
objidi
"blue"
11
objidi
"green"
7
objidi
"green"
11
obj id2
"red"
7
obj id2
"red"
11
objid2
"blue"
7
obj id2
"blue"
11
objid2
"green"
7
objid2
"green"
11
objid3
"red"
7
objid3
'’red"
11
obj id3
"blue"
7 .
obj id3
"blue"
11
objid3
"green"
7
objid3
"green"
11
Декартово произведение содержит все возможные комбинации значений исходных трех коллекций.. ’
Кардинальное число декартова произведения представляет собой произведение кардинальных чисел исходных коллекций. Оно может быть чрезвычайно большим. Предположим, что в декартовом произведении участвуют три коллекции с кардиналь-
ними числами 1 000, 100 000 и 100. Эти размеры приемлемы, если коллекции представляют все экземпляры типа. Но кардинальное число такого декартова произведения составит 10 миллиардов элементов! Минимизация кардинальных чисел выражений коллекций colli может сократить объем обработки, необходимой для вычисления декартова произведения, формируемого директивой from.
На практике до генерации декартова произведения выполняется некоторая фильтрация. В большинстве реализаций запросов декартово произведение никогда явным образом не генерируется, но результат должен отражать математический эквивалент такого декартова произведения.
Директива from
Директива from задает коллекции для формирования декартова произведения. Оба языка, как SQL, так и OQL, поддерживают директиву from, но по-разному. Выражения коллекций colli, задаваемые в OQL-директиве from, не являются простыми именами экстентов, подобными именам таблиц, которые указываются в SQL. В качестве выражения коллекции в OQL-директиве from можно использовать любое выражение запроса, результат которого представляет собой коллекцию. Это могут быть следующие выражения:
экстент;
именованная коллекция;
литеральная коллекция (см. раздел 11.3.1);
атрибут коллекции, встроенный в качестве члена класса;
автономная коллекция, на которую есть ссылка;
коллекция, возвращаемая методом или функцией объекта;
результат бинарной операции над множествами: объединение, пересечение или вычитание (см. раздел 11.3.3.1);
результат конкатенации или формирования подколлекции (см. раздел 11.3.4);
результат преобразования коллекции: listtoset или flatten (см. раздел 11.3.6);
другое выражение запроса select (подзапрос).
Язык SQL позволяет в директиве from задавать представления, которые иногда выполняют эквивалентные операции.
Каждое выражение colli означает коллекцию элементов некоторого типа Ti. Коллекция может иметь любой тип, поддерживаемый средой OQL, включая множество (set), мультимножество (bag), список (list) или массив (array). Любая реализация языка OQL должна поддерживать все типы коллекций, которые поддерживаются в интерфейсе C++. Под элементом в выражении colli подразумевается одно значение типа Ti.
При использовании SQL-запросов, обращенных к реляционной базе данных, компонентом в SQL-директиве from является таблица. Таблица содержит кортежи, которые, как правило, имеют несколько столбцов. Другими словами, кортеж потенциально имеет несколько значений. С “точки зрения” языков C++ и OQL, кортеж подобен структуре (struct), в которой данные-члены соответствуют столбцам некоторой таблицы. В SQL директива from может содержать только таблицы или представления, которые сводятся к таблицам.
Элементы коллекций в OQL могут иметь любой тип т, поддерживаемый в схеме базы данных. Язык OQL предоставляет гораздо большую гибкость относительно поддерживаемых типов данных, позволяя эффективнее использовать композицию, со-
ставленную из различных функций. Результат запроса select может служить в качестве операнда для операции, которая принимает коллекции с элементами типа, выраженного в проекции этого запроса select.
Язык SQL опирается на реляционное исчисление кортежей (tuple relational calculus), которое требует, чтобы компоненты декартова произведения имели тип кортежей. OQL базируется на реляционном исчислении доменов, и в этом случае компоненты декартова произведения могут иметь любой доменный тип, который не ограничивается кортежами. Следовательно, реляционное исчисление доменов является более общим и предлагает более высокую степень гибкости при использовании типов, чем реляционное исчисление кортежей. Таким образом, язык OQL для выражения запросов к базам данных обеспечивает более богатую среду использования типов, чем SQL.
OQL-директива from задает набор разделенных запятыми коллекций, используемых для формирования декартова произведения. Чтобы в других частях запроса ссылаться на значение “текущего” элемента коллекции, каждую коллекцию можно связать с некоторой переменной итератора. Для связи переменной итератора с выражением коллекции используется следующее выражение.
colli as Vi
Ключевое слово as необязательно, поэтому прельщущее выражение можно переписать еще проще.
СО11Х Vi
Например, в следующем запросе для доступа к названиям всех отделов компании используется коллекция с именем Depts.
select d.name() from Depts as d
Переменную итератора Vi можно опустить, и тогда в качестве имени переменной для ссылки на текущий элемент коллекции в других частях запроса используется выражение коллекции colli. Но запросы легче выражать и понимать, если все же не опускать переменную итератора.
В директиве from также можно использовать следующее выражение.
Vi in colli
Но здесь применен старый синтаксис языка OQL, который сохранен для обратной совместимости. Для большей совместимости в язык OQL версии 1.2 стандарта ODMG-93 включен оператор членства in (см. табл. 11.10). SQL-оператор in также используется в качестве оператора членства, но его значение отличается от исходного в языке OQL. Следовательно, OQL-оператор in имеет одно из двух значений, в зависимости от характера использования: в директиве from или в качестве оператора членства. Чтобы избежать путаницы, оператор in лучше использовать только как оператор членства (по аналогии с языком SQL) и отказаться от применения в OQL- директиве from (как показано в последнем примере). В этом случае OQL-запросы станут больше походить на запросы языка SQL.
В выражении коллекции collj директивы from можно использовать еще одну переменную итератора vi, где i < j. (Это ограничение не допускает повторяющихся определений переменных итераторов, чтобы избежать неудачно сформированных запросов.) Этот метод обеспечивает четкое и эффективное навигационное средство для выражения набора связанных объектов. Оно позволяет более точно обозначить коллекции colli, обрабатываемые в запросе. Причем вы можете задать эти выражения в одном месте, а именно в директиве from.
Например, в следующем запросе считывается имя (fname) и фамилия (iname) ребенка (children), родители которого работают в отделе людских ресурсов (Human Resources).
selectc._fname, c._lname
fromDepts d, d.employees e, e._childrenc
whered._name = "Human Resources”
С помощью переменной итератора d будет получен прямой доступ ко всем отделам с именем Human Resources. К служащим этих отделов реализуется прямой доступ из члена employees класса Department посредством ссылки е. А к детям каждого из этих служащих доступ обеспечивается благодаря члену _children. Этот запрос напрямую переходит от объекта к объекту, которые статически связаны в этой базе данных. Ни в одном из экстентов (ни в классе Employee, ни в классе Person) не требуется операций соединения или поиска. Задание переменной итератора, основанное на значении другой переменной итератора, может обеспечить значительное преимущество в производительности. Результат декартова произведения в директиве from ограничен гораздо меньшим набором значений, который приближается к кардинальному значению конечного результата запроса.
При использовании SQL-запросов в реляционной базе данных для установления связей между кортежами таблиц в директиве where задается необходимое условие соединения. Так, следующий SQL-запрос эквивалентен предыдущему OQL-примеру.
select c._fname, c._lname
from Department d, Employee e, Children c
where d._name = "Human Resources" and
d.deptID = e.deptID and c.parentID = e.empID
В этом запросе условия соединения перемешаны со всеми ограничениями булева типа. Такая организация приводит к тому, что директива where содержит выражения с различным назначением, которые направлены на ограничение вариантов, образующих результат запроса. Декартово произведение имеет гораздо большее кардинальное число и требует гораздо большего количества действий для фильтрации в директиве from, чем в эквивалентном OQL-запросе.
OQL-версия этого запроса может отследить известные статические связи между отделами и их служащими, а также между служащими и их детьми. При этом в OQL- запросе доступ обеспечивается только к нужным объектам. В языке SQL для обработки аналогичного запроса необходимо выполнить операцию соединения. Благодаря индексам количество опрашиваемых кортежей можно минимизировать.
Директива where
Директива where содержит булев предикат predicate, который логически вычисляется для каждого элемента декартова произведения, формируемого директивой from. (Использование такого средства оптимизации, как индексы, способно уменьшить количество элементов декартова произведения.) Для ссылки на компоненты декартова произведения в виде выражений предиката можно использовать переменные итераторов, заданные в директиве from. Многие OQL-операторы возвращают булево значение, которое вполне приемлемо для обозначения ограничений в директиве where. В результате запроса остаются только те элементы декартова произведения, которые истинны для заданного булева предиката; элементы с ложным значением этого предиката отбрасываются.
Доректива select
Запрос select возвращает некоторую коллекцию. Тип этой коллекции определяется формой запроса в соответствии с табл. 12.2, тип элементов коллекции — проекцией projection, заданной в директиве select. Образно говоря, этап создания проекции при вычислении запроса можно представить в виде ввода некоторой коллекции элементов, полученных в результате применения других директив запроса. Тип этих элементов имеет следующий вид.
struct (Vi: Ti, V2:T2, . . . , V„:Tn)
Каждый член Vi представляет собой переменную итератора, определенную в директиве from. Эти структуры содержат значения из декартова произведения, которые остаются после выполнения всех операций фильтрации. Проекция projection — это единое выражение запроса, которое использует значения, содержащиеся в структурах для получения конечного результата. Оно может содержать выражения, использующие переменные итераторов Vi для ссылки на значения текущего элемента.
Элемент коллекции, возвращаемый запросом select, представляет собой одно значение данного типа. Тип этих элементов может совпадать с одним из следующих типов:
ссылка на объект;
значение объекта (включающее реальные значения атрибутов);
тип struct;
примитивный тип (например, int, float, string);
коллекция.
Это может быть любой тип, поддерживаемый в языке OQL.
Проиллюстрируем некоторые возможности на примере приведенных ниже запросов. Итак, следующий запрос получает доступ к бюджету всех отделов компании.
select d._budget from Depts d
Поскольку в этом запросе используется один класс, из выражения запроса можно спокойно удалить переменную итератора, не опасаясь какой-либо двусмысленности. Правила области видимости для имен рассматриваются в разделе 12.9. Следующие запросы возвращают мультимножество с элементами целочисленного типа.
select _budget
from Depts
Функциональная природа языка OQL позволяет использовать такие запросы непосредственно в качестве операнда другого выражения запроса. Например, следующий запрос получает общий бюджет всех отделов.
sum(select _budget from Depts )
Тип возврата запроса
selectd
fromDeptsd
whered._budget>1000000
представляетсобоймультимножество (bag) ссылок на экземпляры класса
Department. Эти ссылки указывают на такие экземпляры класса Department, в которых бюджет превышает миллион. Для получения доступа к имени и бюджету каждого отдела необходимо сформировать структуру определенного типа.
select struct(name: d->_name, budget: d->_budget)
fromDepts d
whered._budget > 1000000
Имена членов структуры (name, budget) указываются слева от двоеточия, а тип и значение каждого члена структуры определяются выражением запроса справа от двоеточия. Спецификация структуры (struct) описана в разделе 11.1.7. Следующий запрос показывает, как в виде результата запроса получить реальное значение экземпляра класса Department.
select *d
fromDepts d
whered._budget > 1000000
Этот результат имеет тип struct, причем в этой структуре должен присутствовать член для каждого атрибута класса Department, включая встроенные коллекции, которые содержат ссылки на другие объекты (члены employees и projects).
В этих примерах выражения, заданные в директиве select, просто получают доступ к атрибутам. Но они могут иметь различную степень сложности. Вы можете использовать любое допустимое выражение запроса, включая выражение, которое выполняет другую директиву select.
В языке OQL тип результата может быть любым, в то время как SQL-запрос всегда возвращает таблицу кортежей. Это — еще одно преимущество языка OQL, основанное на реляционном исчислении доменов, а не реляционном исчислении кортежей. OQL- запрос может возвратить эквивалент кортежа, если в проекции задана структура (struct). В приведенном выше запросе, возвращающем название и бюджет отделов, использована структура, эквивалентная кортежу.
Если проекция projection состоит из единственного символа *, результат запроса представляет собой коллекцию типа struct, в которой каждый член позиционно соответствует переменной итератора, объявленной в директиве from.
struct(Vi:Ti, V2:T2, . .. , Vn:T„)
В целях синтаксического удобства и обеспечения совместимости с языком SQL можно также задать список выражений проекций, разделенных запятыми. Каждая проекция должна иметь одну из следующих форм записи.
выражение as идентификатор
идентификатор : выражение
выражение
Идентификатор должен существовать для каждого выражения проекции. Последняя форма записи действительна только в том случае, если имя идентификатора однозначно “вытекает” из выражения, например если это имя соответствует имени атрибута. Список, состоящий из п выражений проекций, создает тип, определяемый следующим образом.
struct! идентификатор! : выражение!, идентификатор2 : выражение^
идентификатор,, : выражениеп ) ;
Тип каждого члена структуры выводится из типа выражения проекции. Например, в следующем запросе структура состоит из строки и целого значения.
select d._name, d._budget
from Depts d..
Чтобы вернуть несколько значений для каждого элемента в директиве select, проекция должна иметь тип struct. Использование списка выражений, разделенных запятыми, играет роль синтаксического сокращения для более сложной спецификации структуры, определяемой проекцией.
Директивы group бу и having
Язык OQL поддерживает директиву group by аналогично языку SQL. Она позволяет разделить элементы декартова произведения на отдельные группы на основе заданного критерия. При данном запросе select (select_query), определенном в разделе 12.3, выражении структуры partition_attributes и предикате группы group_predicate синтаксис выражения group by имеет следующий вид.
select_query group by partition_attributes
ИЛИ
select_query group by partition_attributes having group_predicate
Компонент атрибутов разделения partition_attributes определяет основу для разделения элементов декартова произведения на отдельные группы. Атрибут разделения может быть выражением любого типа. Группа определяется для каждого уникального набора значений атрибутов разделения. Каждый элемент декартова произведения будет связан с одной группой на основе его значений для атрибутов разделения.
Каждый элемент в выражении select_query оценивается относительно атрибутов разделения на группы. Элементы с одинаковыми значениями для всех атрибутов разделения помешаются в одну и ту же группу. После группирования результат преобразуется (и потенциально сворачивается) в множество структур. Каждая структура содержит значение для каждого атрибута разделения и мультимножество, содержащее все элементы выражения select_query, которые имели одинаковые значения для атрибутов разделения.
Переменные итераторов vi выражения select_query связываются с выражениями коллекций colli, которые содержат элементы типа Ti (где 1=1, п). Директива group by вводит атрибуты разделения ра3 типа PTj (где j=l,m) для ш атрибутов разделения. Тип результата применения директивы group by определяется следующим образом.
set < struct ( pai : PTi, pa2 : PT2, ■ . ■ , pam : PT,,, partition : bag<struct (Vj: Ti, V2:T2, - - - , Vn:Tn)> )
Это множество содержит некоторый элемент структуры для каждого уникального множества значений атрибутов разделения. Каждый такой элемент структуры включает данное-член, которое содержит значение атрибута разделения. Он также содержит мультимножество partition, элементы которого являются значениями итераторов выражения select_query, имеющими такие же значения атрибутов разделения. На каждый элемент результата выражения select_query существует ссылка в мультимножестве partition одной и только одной структуры приведенного выше множества set.
Рассмотрим следующий запрос.
select from where group byselect from where group bystate, city, avg(select p.e._salary from partition p)
select from where group by
select from where group by
Depts d, d.employees e
d._budget > 10000000
state: e._address.state, city: e._address.city
Директивы from и where запрашивают отделы с бюджетом, превышающим 10 миллионов, вместе с данными о служащих. Предположим, что отделы, показанные в табл. 12.3, имеют достаточно большие бюджеты.
Таблица 12.3. Отделы с бюджетом, превышающим 10 миллионов
Отдел
ObjectID (идентификатор объекта) Название
depti
"Network Performance"
dept2
"Switching Architecture"
dept3
"Network Operations Engineering"
В этих отделах работают служащие, сведения о которых приведены в табл. 12.4. Промежуточный результат представляет собой мультимножество структур. Каждая структура содержит ссылки на экземпляры классов Employee и Department, которые соответствуют первым двум столбцам в табл. 12.4.
Затем это мультимножество группируется на основе названий штата и города, где проживает служащий. Промежуточный результат этого действия представляет собой множество, описанное ниже. Его элементы имеют тип struct с членом для каждого атрибута разделения (state или city), а также мультимножеством partition, которое содержит элементы декартова произведения с одинаковыми значениями атрибутов разделения. Они содержатся в структуре и состоят из значений переменных итераторов end, которые становятся именами членов структуры struct в мультимножестве partition.
set<struct( state : char *, city : d_String, partition : bag<struct(d : d_Ref<Department>, e : d_Ref<Employee>)> )
Таблица 12.4. Данные о служащих отделов с большим бюджетом
Служащие
ObjectID
DeptID
„address.state
„address.city
„salary
етрг
depti
"OH"
"Gahanna"
78000
emp2
depti
"NJ"
"Holmdel"
89000
етрз
depti
"IL"
"Naperville"
81000
emp4
depti
"OH"
"Reynoldsburg"
75000
emp5
depti
"NJ"
"Holmdel"
87000
empe
depti
"OH"
"Gahanna"
77000
emp?
depti
"IL"
"Naperville"
83000
emp8
depti
"OH"
"Reynoldsburg"
77000
етрэ
dept2
"NJ"
"Holmdel"
85000
emp10
depti
"NJ"
"Red Bank"
83000
етри
dept2
"NJ"
"Red Bank"
85000
Окончание табл. 12.4
Служащие
ObjectID
DeptID
_address. state
_address.city
„salary
empu
dept2
"IL"
"Naperville"
81500
empis
dept 2
"IL"
"Indian Hill"
85000
empn
dept2
"IL"
"Naperville"
82500
empis
dept3
"OH"
"Gahanna"
79000
empL6
dept3
"NJ"
"Red Bank"
84000
empi?
dept3
"OH"
"New Albany"
84000
empig
dept3
"NJ"
"Murray Hill"
96000
empi9
dept3
"NJ"
"Murray Hill"
94000
emp20
dept3
"OH"
"New Albany"
80000
Для этих данных было получено следующее множество.
set (
struct ( "IL", "Indian Hill", bag ( struct (dept2, emp13) ) ) , struct("IL", "Naperville", bag( struct (depti, emp3), struct (depti, emp3) , struct (dept2, empi2), struct (dept2, empulll,
struct("NJ", "Holmdel", bag( struct(depti, emp2), struct (depti, emps) , struct (depti, emps) ) ) ,
struct ("NJ", "Murray Hill", bag( struct(dept3, empis), struct(dept3, empis) ) ) ,
struct ("NJ", "Red Bank", bag( struct (depti, етрю), struct (dept2, empu), struct (dept3, empi6) ) ) ,
struct("OH", "Gahanna", bag( struct(dept,, empi), struct (depti, етрб) , struct (dept3, empis) ) ) ,
struct("OH", "New Albany", bag! struct (dept3, empu), struct (dept3, emp20) ) ) ,
struct("OH", "Reynoldsburg", bag(struct(depti, emp»), struct (depti, empg) ) )
)
Директива select генерирует окончательный результат запроса, который состоит из следующих составных частей:
названия штата (посредством использования имени атрибута разделения state);
названия города (посредством использования имени атрибута разделения city);
средней зарплаты служащих, проживающих в одном и том же городе.
Для вычисления средней зарплаты компонент partition преобразуется из мультимножества структур struct в коллекцию целых (значений зарплаты). Запрос выполняется на данном мультимножестве (группе разделения), и для доступа к данным о зарплате каждого служащего в этом мультимножестве используется член структуры е. (Обратите внимание, что любой запрос в качестве компонента конечного результата также может возвратить само это мультимножество.) Конечный результат запроса имеет следующий вид.
set (set (
set (
set (
structC’IL", "Indian Hill", 85000), structC’IL", "Naperville", 82000), struct("NJ", "Holmdel", 87000), struct("NJ", "Murray Hill", 95000), structC’NJ", "Red Bank", 84000), struct("OH", "Gahanna", 78000), struct("OH", "New Albany", 82000), struct("OH", "Reynoldsburg", 76000),
Даже несмотря на то что промежуточный результат работы директивы group by представляет собой множество, конечным результатом является мультимножество. Проекция в директиве select может иметь выражения, которые приводят к наличию в результате запроса повторяющихся значений. Если для приложения важно иметь в результате только уникальные значения, необходимо использовать ключевое слово distinct.
Некоторые реализации могут “сами” генерировать результаты в отсортированном порядке. Причем структуры отсортированных данных могут использоваться не только на конечном этапе (при выдаче результата), но и на промежуточных этапах, что способствует повышению производительности. Но приложение не должно полагаться на этот побочный эффект. Если конечный результат должен быть отсортирован, необходимо использовать директиву order by.
Если директива group by способна разделить данные на группы, то директиву having можно использовать для удаления из результата запроса целых групп. Директива having определяет булев групповой предикат group_predicate, который вычисляется для каждого элемента множества, построенного директивой group by. Она фильтрует множество до обработки проекции в директиве select. Группа, присутствующая в множестве, включается в конечный результат запроса только в том случае, если групповой предикат group_predicate имеет значение true.
Групповой предикат group_predicate может содержать выражения, которые используют атрибуты разделения и мультимножество partition. Поскольку partition является мультимножеством (bag), любые выражения запросов, которые могут быть выполнены на любом мультимножестве, можно применить к мультимножеству partition либо в директиве having, либо select. Усложним немного предыдущий пример запроса таким образом, чтобы в результат не входили города, в которых средняя зарплата не превышает значение 82 500. При этом нас также интересует количество служащих в каждой группе.
selectstate, city, avg(select p.e._salary from partition p) ,
count(partition)
fromDepts d, d.employees e
whered._budget > 10000000
group bystate: e._address.state,city:e._address.city
havingavg(select p.e._salaryfrompartitionp)>82500
При выполнении этого запроса результат будет иметь следующий вид.
set (
structC’IL", "Indian Hill", 85000, 1),
struct("NJ", "Holmdel", 87000, 3),
structC’NJ", "Murray Hill", 95000, 2),
structC’NJ", "Red Bank", 84000, 3),
Директива having подобна директиве where. Директива where имеет предикат для фильтрации элементов из декартова произведения директивы from. Директива group by применяет результат директивы where в качестве входных данных и группирует элементы на основе спецификации атрибутов разделения. Затем директива having использует заданный предикат для удаления целых групп, созданных директивой group by. Следовательно, как директива where, так и директива having отфильтровывают элементы из некоторого исходного набора.
Директива order hy
Результаты запросов можно упорядочить. Если дано выражение запроса select_query вида
select projection from Ci [where predicate]
[group by partition_attributes [having group_predicate]]
(в квадратные скобки заключены необязательные директивы) и выражение expri, то выражение запроса
select_query order by ехрп, expri, . .. , ехргп
возврашает элементы коллекции-результата запроса select_query в виде списка (list), упорядоченного на основе выражений expri. Эти элементы сначала упорядочиваются на основе выражения expri; затем все элементы с равным значением для выражения expri упорядочиваются на основе выражения expri и так продолжается для всех заданных выражений expri. За каждым выражением ехрг± может следовать одно из ключевых слов: asc или desc, что означает упорядочение по возрастанию (spending) или убыванию (zfecending). Если ни одно из этих слов (asc или desc) не следует за выражением expri, то упорядочение в этом случае выполняется по правилу, действующему для предыдущего выражения expri-i. По умолчанию для выражения expri действует упорядочение по возрастанию.
Следующий запрос возвращает данные обо всех служащих отдела сетевых архитектур (Network Architecture), отсортированные в порядке снижения жалованья (от самого высокого к самому низкому). Кроме того, фамилии и имена служащих с одинаковой зарплатой расположены в алфавитном порядке.
selecte._lname, e._fname
fromNetwork_Architecture.employees e
order bye._salary desc, e._lname asc, e._fname
Если в отделе сетевых архитектур найдутся служащие-однофамильцы, то для установления порядка будут проанализированы (с точки зрения алфавита) их имена.
Соединения
Выражение коллекции, определенное в директиве from, может бьпь выведено из выражения пути, использующего переменную итератора для другой коллекции в директиве from. Этот подход позволяет использовать заранее известные связи между объектами для ограничения декартова произведения за счет включения только связанных объектов, требуемых запросом. В этом случае реализация может выполнить эффективный навигационный доступ к связанным объектам путем ограничения кардинального числа и объема вычисления декартова произведения. Но иногда во время работы приложения возникает необходимость в динамическом установлении связей между объектами, часто посредством выражения ограничений на значения атрибутов объектов.
Условие соединения (join condition) — это булево выражение, которое определяет ограничения, накладываемые на значения элементов декартова произведения. Элементы, для которых это ограничивающее условие не выполняется, удаляются из результата. Условия соединения задаются в директиве where и являются единственным средством установления связей в языке SQL. Их также можно определить и в OQL-запросе.
При именованной коллекции Depts, содержащей все отделы, и именованной коллекции Projects, определяющей все проекты компании, следующий запрос возвращает все отделы, названия которых совпадают с названиями проектов, разрабатываемых в других отделах.
selectdistinctp.name, d._name
fromDepts d,Projects p
whered != p->department andd._name = p.name
order byp.name
Отслеживание существующих в схеме связей с помощью выражений пути или определения переменной одного итератора в терминах другого предпочтительнее использования условия соединения. Но если связи существуют в схеме неявно и могут быть установлены динамически на основе некоторых значений, то в этом случае как раз уместно воспользоваться условием соединения в директиве where.
Определение именованных запросов
Поскольку язык OQL поддерживает композицию функций, мы можем определить довольно сложные запросы, которые содержат запросы-операнды, вложенные на произвольную глубину. Чтобы упростить громоздкий запрос, с некоторым выражением запроса можно связать определенное имя и использовать его вместо выражения запроса в качестве соответствующего компонента внутри большого запроса. Выражения запросов можно определить один раз, а затем многократно использовать их в других выражениях запросов.
При данном имени идентификатора (name) и выражении запроса (query_expr) выражение
define [query] патент, Х2, . . . , xn) as query ехрг
является выражением определения запроса, которое связывает имя name с запросом query_expr посредством переменных запроса лц, используемых в выражении запроса query_expr. Слово query необязательно. Определение запроса не может быть рекурсивным.
Если определение запроса не требует никаких параметров, его запись может иметь следующий вид.
define name as query_expr
Если именованный запрос используется без параметров, круглые скобки не нужны.
Например, следующее выражение определения запроса возвращает данные обо всех служащих, имя которых (first name) Bryce.
define all_emps_named_bryce as
selecte
fromEmployees e
wheree._fname = "Bryce"
Создав это определение запроса, можно использовать его имя автономно вместо самого запроса.
а 1 l_emps_n ame d_b гусе
Результат выполнения этого запроса представляет собой мультимножество служащих с именем Bryce. Следующее определение запроса возвращает данные обо всех служащих с указанном фамилией, проживающих в заданном городе.
define emp_name_in_city(n,с) as selecte
fromEmployeese
wheree._lname = n and e,_address.city = c
После создания такого определения следующий запрос вполне допустим.
emp_name_in_city("Mathie", "Louisville")
Имя запроса name нельзя перегружать различным количеством аргументов, т.е. их количество всегда должно соответствовать количеству аргументов в определении запроса. Имя выражения запроса не должно совпадать с любым именованным объектом в базе данных, именем метода или функции класса либо именем класса. Повторное использование имени не допускается, поскольку это приводит к неоднозначности.
Каждый раз, когда при выполнении запроса встречается какое-нибудь выражение функции, делается попытка связать его с функцией или методом, определенными в схеме базы данных. Если такая попытка оказывается неудачной, но при этом существует определение запроса с нужным именем и верным количеством параметров, выполняется соответствующая подстановка выражения запроса (guery_expr) и его переменных. Следующее определение запроса генерирует булев результат, который будет истинным, если менеджер отдела сетевых архитектур зарабатывает не меньше других служащих этого отдела.
define NetArchMgrPaidMost as
Network_Architecture->_manager._salary = max(select distinct e._salary
from Network_Architecture->employees e)
Определение запроса хранится в базе данных. Оно остается активным до тех пор, пока его не заменит новое определение запроса с таким же именем или оно не будет удалено следующей командой.
delete definition_name
Например, предыдущее определение запроса можно удалить, выполнив следующий оператор.
delete emp_name_in_city
Подзапросы
Поскольку язык OQL основывается на композиции функций, в такой среде использование подзапросов вполне естественно. Выражение запроса состоит из оператора и набора операндов. Каждый операнд может быть произвольным выражением запроса. Следовательно, любой операнд выражения запроса может быть подзапросом. Любое выражение запроса, которое в качестве операнда принимает выражение коллекции, может использовать запрос select для построения этого выражения коллекции. Определения запросов также можно применять как эффективный механизм упрощения спецификации более сложных вложенных запросов.
Например, определение запроса
define all_brookes as selecte
■ from •Employees e■
wheree. fname = "Brooke"
можно использовать в спецификации следующего запроса.
define highly paid_brookes as selectstruct(fname:b._fname,
Iname: b._lname, sal: b.salary) fromall_brookes b
order byb.salary desc
Поскольку в этом запросе используется директива order by, коллекция результата будет представлять собой список структур, упорядоченный по убыванию зарплаты. Предыдущий запрос эквивалентен следующему, в котором не используется определение запроса.
selectstruct(fname: b._fname,
Iname: b._lname, sal: b.salary)
from( select e from Employees e
where e._fname = "Brooke") b order byb.salary desc
Следующий запрос возвратит фамилию самого высокооплачиваемого служащего, которого зовут Brooke.
define highest_paid_brooke as
first(highly_paid_brookes).Iname:
Поскольку коллекция, являющаяся результатом выполнения запроса highly_paid_brookes, уже упорядочена по убыванию зарплаты, при доступе к первому элементу списка получим данные о самом высокооплачиваемом служащем, которого зовут Brooke.
Правила для областей видимости имен
Когда пользователи формируют относительно сложный запрос с вложенными выражениями запросов и несколькими директивами select, очень важно, чтобы они понимали область видимости имен, используемых в запросе. Пользователь запроса может предполагать, что некоторое имя относится к одной области видимости, а на самом деле оно может принадлежать другой, гораздо более обширной. Подобные недоразумения могут привести к неверному результату запроса, который обязательно введет пользователя в заблуждение.
Запрос select может охватывать несколько коллекций с элементами различного типа. Тип элемента коллекции может иметь атрибут с именем, которое является уникальным для всех коллекций, упоминаемых в директиве from. В этом случае не возникнет вопроса, элемент какой коллекции имеется в виду; поэтому такое имя можно использовать самостоятельно, без соответствующей переменной итератора. Но если в директиве select некоторое имя атрибута связывается с несколькими коллекциями, переменная итератора обязательна. В противном случае будет непонятно, на элемент какой коллекции выполняется ссылка. Для четкости и упрощения записи запроса рекомендуется всегда использовать переменные итераторов.
Имена переменных итераторов
Неявные и явные имена для переменных итераторов вводятся в директиве from. Если коллекция в директиве from не имеет переменной итератора, имя этой коллекции неявно служит в качестве имени переменной итератора. Область видимости таких имен охватывает все части соответствующего запроса select, включая все выражения вложенных запросов.
Имена групп разделения
Если в запросе используется директива group by, то имя partition вводится подобно любым задаваемым именам атрибутов групп разделения. Эти имена находятся в области видимости, которая включает директивы having и select, связанные с директивой group by, со всеми выражениями вложенных запросов внутри этих директив.
Поиск имен
При появлении в запросе некоторого имени его поиск осуществляется в следующем порядке.
Переменная в текущей области видимости.
Именованный запрос, введенный выражением define.
Именованный объект.
Имя переменной итератора для атрибута или операции в текущей области видимости при отсутствии неоднозначности.
Резюме
Этой главой завершается описание языка OQL в настоящей книге. Функциональная природа OQL позволяет представить в виде выражений даже очень сложные запросы. В этой главе рассмотрены предикаты и директива select, которая хорошо знакома тем, кто уже работал с языком запросов SQL. Здесь также отмечены некоторые различия между OQL- и SQL-директивами select. В частности, подчеркивается гибкость использования типов и возможность эффективно выразить навигацию по объектам в OQL-директиве from. А в главе 13 вы узнаете, как встраивать OQL- запросы в С++-приложения.
Глава 13
Среда «обитания» OQL
В этой главе акцент ставится главным образом на интерфейсах C++, предназначенных для выполнения OQL-запросов. Запрос может быть «замешан» на использовании коллекций или общении с базой данных. Каждая реализация, которая поддерживает язык OQL, обеспечивает С++-интерфейс и интерактивный интерфейс; причем интерактивные интерфейсы различны для разных продуктов. Запросы либо вводятся в интерактивную среду оболочки, либо размещаются в файле, который считывается программой запроса. Данная глава завершается анализом реализации запросов в архитектуре объектных баз данных.
Запросы коллекции в C++
В классе d_Collection<T> предусмотрены четыре функции для отбора элементов, которые истинны для некоторого предиката OQL-запроса. Этот предикат может быть любым булевым выражением запроса. Данный запрос эквивалентен законченному OQL-запросу, который выполняет директиву select для одной коллекции и содержит при этом директиву where, состоящую из булева предиката. В этом предикате для ссылки на текущий элемент итерации можно использовать ключевое слово this, которое играет роль имени переменной итератора, связанной с данной коллекцией в директиве from.
Функция exists_element возвращает истинное значение, если в коллекции существует элемент, для которого истинен данный предикат; в противном случае она возвращает ложное значение. Предположим, что коллекция представлена выражением coll, а булев OQL-предикат — выражением predicate. Функция-член exists element является эквивалентом для следующего автономного OQL-запроса.
exists this in coll : predicate
Допустим, мы хотим определить, зарабатывает ли какой-нибудь из служащих отдела сетевых архитектур больше $100 000. Напомню, что имя "Network_Architecture" связано с экземпляром класса Department. Искомый результат можно получить, выполнив следующий фрагмент программы.
d_Database db;// предположим использование открытой базы данных
int result = 0;// логический индикатор
// истинности запроса d_Ref<Department> dept = db.lookup_object("Network_Architecture"); if( !dept.is_null() ){
result = dept->employees.exists_element("this->_salary > 100000"); }
Переменная result будет содержать истинное значение, если в отделе сетевых архитектур найдется служащий с заработком, превышающим $100 000. Переменная this указывает на текущий элемент в итерации.
Функция select element возвращает значение первого найденного элемента, который удовлетворяет булеву предикату. При выполнении следующего фрагмента программы осуществляется доступ к первому экземпляру класса Employee с зарплатой, превышающей $100 000. Предположим, переменная dept была инициализирована ссылкой на нужный отдел.
d_Ref<Employee> emp;
emp = dept->employees.select_element("this->_salary > 100000");
Функция query заполняет коллекцию, предоставленную вызывающей стороной, всеми элементами, которые удовлетворяют заданному предикату. Допустим, такая коллекция представлена выражением coll, а булев OQL-предикат — выражением predicate. Тогда функция-член query является эквивалентом для следующего автономного OQL-запроса. selectthis
fromcoll this
wherepredicate
Следующий запрос возвращает множество, содержащее всех служащих отдела сетевых архитектур с фамилией Kirsh. Предположим опять-таки, что переменная dept была инициализирована ссылкой на этот отдел.
d_Set<d_Ref<Employee> > kirshs;
dept->employees.query(kirshs, "this->_last_name = \"Kirsh\"");
После выполнения этого запроса итератор можно использовать для обхода всех ссылок на экземпляры класса Employee в полученном результате. Поскольку значение результата представляет собой другую коллекцию, в ней можно выполнять и иные операции, включая другой запрос.
Функция-член select возвращает итератор, чтобы приложение могло опросить элементы, которые удовлетворяют заданному предикату. Предыдущий фрагмент программы можно заменить следующим образом.
d_Iterator<d_Ref<Employee> > iter;
iter = dept->select(kirshs, "this->_last_name = \"Kirsh\"");
Затем этот итератор можно использовать для доступа к каждому элементу результата. Такой подход избавляет от необходимости в явном виде формировать результат в виде коллекции, что особенно важно, если кардинальное число результата довольно велико.
Запросы дм шшентн [d JExtent<T>|
Введенное в версии 2.0 стандарта ODMG средство работы с экстентами поддерживает для экстентов те же функции запросов.
Предположим, для класса Employee определен некоторый экстент. При выполнении следующего фрагмента программы будут получены все экземпляры класса Employee, соответствующие служащим, проживающим в городе New Albany.
d_Database *db;// предположим использование указателя
// на открытую базу данных
d_Iterator<d_Ref<Employee> > iter;
d_Ref<Employee> emp;
d_Extent<Employee> Emps(db);
iter = Emps.select("this->_address_city = \"New AlbanyV"); while( iter.next(emp) ){
// Любые действия над экземплярами класса Employee,
// адресуемыми ссылками emp.
}
Запросы к базам данных в среде C++
С++-приложение также может передать базе данных OQL-запрос. Такую возможность следует использовать, если выполнение запроса не ограничивается одной коллекцией. Построенный с использованием класса d_OQL_Query, запрос можно выполнять многократно, причем с различными значениями параметров.
При первоначальном выполнении запроса его текст анализируется, компилируется и оптимизируется. В некоторых реализациях последующие вызовы этого запроса, но с другими параметрами, могут обойтись без дополнительных расходов системных ресурсов (т.е. вполне достаточно затрат на первоначальную обработку запроса).
Экземпляр класса d_OQL_Query содержит спецификацию запроса. Законченный запрос передается в качестве первого параметра функции d_oql_execute. Второй параметр этой шаблонной функции определяет тип ожидаемого результата. Этот параметр указывает место размещения результата запроса. Если тип результата запроса не совпадает с типом второго параметра или любой параметр, используемый в конструкции запроса, имеет неверный тип, то генерируется исключение d_Error_QueryParameterTypeInvalid.
Прежде чем выполнять запрос, его нужно сначала построить. В приложении Б содержится полное описание интерфейса d_OQL_Query. Экземпляр этого класса можно инициализировать символьным массивом (тип d string) или другим объектом класса d_OQL_Query. Оператор сдвига влево « перегружен для работы с несколькими типами, чтобы разрешить присоединение значений к концу запроса. Множество поддерживаемых в данном случае типов приведено в приложении Б. Эти функции позволяют наращивать конструкцию спецификации запроса.
Запрос может иметь параметры, значения которых предоставляются непосредственно при его выполнении. Они обозначаются подстрокой формы Si, где i — номер, указывающий на операнд i-ro последующего вызова оператора «. Первый операнд имеет обозначение S1. Если любой из Si параметров запроса не имеет соответствующего значения, предоставляемого при выполнении этого запроса, генерируется исключение d_Error_QueryParameterCountlnvalid. Если какой-нибудь параметр запроса имеет неверный тип, генерируется исключение d_Error_QueryParameterTypeInvalid.
При повторяющихся передачах запроса функции d_oql_execute исходная спецификация запроса в экземпляре класса d_OQL_Query не меняется. Но параметры запроса при этом очищаются и должны заново передаваться для каждого последующего выполнения запроса.
Например, следующий запрос возвращает множество служащих компании, которые работают над проектом без партнеров (в одиночку).
d_OQL_Query query("distinct(select e from Depts d,\ d.projects p, p.members e\ where unique(p.members) ) ") ;
d_Set<d_Ref<Employee> > work_alone; d_oql_execute(query, work_alone);
После выполнения запроса можно создать итератор из множества work_alone, чтобы опросить всех служащих (все экземпляры класса Employee), которые в одиночку работают над проектом.
Предыдущий запрос не имеет ни одного параметра. Предположим, что написана функция для вывода имен служащих с заданной фамилией, работающих над проектом, стоимость которого меньше указанной суммы. В этом запросе используется объект с именем Depts, который содержит ссылку на каждый экземпляр класса Department.
void
print employee first_names(long cost, const char *last_name) { ”
d_Set<d_Ref<Employee> > emps; d_Iterator<d_Ref<Employee> > iter; d_Ref<Employee> e;
static d_OQL_Query q("select unique e from Depts d, \ d.projects p, p.members e\ where p.cost < $1 and e.last_name = $2");
q « cost;
q « last_name;
d_oql_execute(q, emps);
iter = emps.create_iterator();
while( iter.next(e) ){ cout << e->first_name() « endl;
} }
После вызова функция d_oql_execute не завершит работу до тех пор, пока все элементы результата запроса не будут помещены в экземпляр множества d_Set с именем emps. Если в результате запроса содержится большое количество экземпляров, то заполнение коллекции элементами результата может быть сопряжено с большими (причем неоправданными) затратами системных ресурсов, особенно если приложение итерирует результат только в одном направлении. Версия 2.0 стандарта ODMG содержит новую функцию d_oql_execute, которая возвращает экземпляр класса d_Iterator для опроса элементов результата запроса. Если использовать новую функцию, то в предыдущий фрагмент программы нужно внести изменения, заменив строки
d_oql_execute(q, emps); iter = emps.create_iterator();
следующей строкой.z
d__oql_execute (q, iter);
При использовании этой функции отпадает необходимость в построении коллекции, а результаты запроса можно передавать приложению более мелкими порциями,
по мере необходимости обеспечивая перемещение итератора. Преимущество новой функции d_oql_execute в том, что она быстрее возвращается к приложению. Более того, если приложению понадобится завершить итерацию до ее полного окончания, оставшиеся элементы результата запроса не придется передавать приложению, что (само собой) сократит затраты системных ресурсов. Эта функция сходна с курсором базы данных.
Дрхнтектдрные решения механизма обработка запросов
При реализации языка запросов для объектных баз данных, разработчики должны учитывать многочисленные архитектурные решения. Большинство реализаций объектных баз данных опирается на архитектуру класса клиент/сервер, но, по сравнению с реляционной базой данных, значительная функциональная нагрузка у них ложится на «плечи» клиента. Где же тогда выполняется обработка запросов? Ее можно поручить серверу или клиенту, или можно организовать комбинированный (распределенный) вариант. Именно местом обработки запросов и отличаются существующие в настоящее время реализации баз данных.
Если реализация запросов подразумевает предоставление объектного интерфейса, который соответствует абстракциям, определяемым С++-классами приложения, то она должна обладать возможностью выполнять операции (С++-функции-члены), которые были определены для объектов. Для поддержки этой возможности используются специальные архитектурные решения.
Определение места обработки запросов
Если обработка запросов происходит в сервере, то он может выполнить все поисковые операции и передать клиенту лишь результаты. При такой организации минимизируется объем затрат на коммуникационные (клиент/сервер) нужды. Недостаток этого подхода в том, что все заявки пользователей базы данных на создание запросов размещаются только в среде сервера базы данных. При отсутствии многопроцессовой или многопотоковой обработки заявок на создание запросов значительная нагрузка на сервер может привести к замедлению получения ответов клиентами в многопользовательской среде.
При обработке запросов на стороне клиента большая часть нагрузки переносится на компьютер клиента. Это значит, что серверу достается меньшая часть обработки для удовлетворения каждого клиента, что позволяет обслужить большее их количество. Клиенты могут быть размещены на отдельных компьютерах, поэтому общие расходы на обработку запросов распределяются между несколькими компьютерными средами. Недостаток этого решения в том, что выполнение запроса требует передачи содержимого базы данных от сервера к клиенту, что существенно увеличивает затраты на коммуникационные (клиент/сервер) нужды по сравнению с обработкой запросов на стороне сервера. Разделение функций часто имеет как положительный эффект, связанный с увеличением параллелизма, так и отрицательный, выраженный в увеличении коммуникационных расходов. '
Выпплненпе ппераций
Если механизм обработки запросов позволяет выполнять некоторые операции над объектами, значит, эти операции должны быть доступны при выполнении запросов. Обычно общедоступно только некоторое подмножество функций-членов. Разработчик схемы базы данных должен определить, какие функции-члены должны быть включе-
187187Глава 13. Среда «обитания» GX3L
187
187
ны в это подмножество, и связать его с механизмом обработки запросов. Обычно в разработке выделяется целый этап, посвященный построению программы по обработке запросов, которая бы включала все функции-члены, специально созданные для данного приложения. Метод, используемый для поддержки вызова операций на языке запросов, зависит от того, где выполняется обработка запросов: на стороне клиента или сервера.
Фирмы-производители обычно не разрешают пользователям связывать С++-код приложения с процессом сервера базы данных. Они опасаются, что «потерявшиеся» указатели, «прописанные» в коде пользователя, могут исказить содержимое кэша базы данных. Если обработка запросов выполняется в процессе клиента, нужные операции можно было бы непосредственно связать с механизмом обработки запросов. Такое решение оказывается самым простым для реализации и не создает никаких проблем, связанных с целостностью базы данных.
Если обработка запросов выполняется в сервере, то можно поместить необходимые операции в отдельный процесс и заставить сервер обращаться к ним посредством вызова удаленных процедур. Сначала в этот процесс передается соответствующий объект, а затем вызываются нужные операции. В этом случае процессу, выполняющему запрос, не нужно напрямую компоноваться с программой приложения. В существующих реализациях используются оба описанных выше решения.
Часть III
Архитектура
Глава 14
Архитектурные решения объектных баз данных
Архитектура объектных баз данных состоит из множества компонентов. Каждая фирма-производитель, чтобы построить законченную среду функционирования базы данных, по-своему определяет состав этих компонентов и характер их взаимодействия. Все проектировочные решения содержат некоторые компромиссы, призванные оптимизировать конкретные возможности архитектуры фирмы-производителя, и их выбор оказывает существенное влияние на пригодность данной реализации для того или иного приложения.
Разработчики приложений должны понимать основополагающие принципы архитектурных решений, чтобы определить, будут ли они отвечать их требованиям. Это значит, что разработчики должны иметь отнюдь не поверхностное представление о компонентах, возможных вариантах дизайна и выбранных производителями компромиссах.
Компоненты
В архитектуре объектной базы данных компоненты можно разделить на несколько категорий. Каждый компонент обращается к данным, обрабатывает их или выполняет операции коммуникации между компонентами (или берет на себя все три вида работы). К таким архитектурным компонентам высокого уровня относятся
аппаратные ресурсы (сети, компьютеры, процессоры, запоминающие устройства);
программные средства сервера базы данных (базы данных, процессы сервера базы данных, потоки сервера базы данных, кэш базы данных);
среда приложения клиента (процессы, потоки, кэш клиента, функциональные средства приложения).
На приведенных ниже рисунках показаны компоненты и взаимосвязи между ними. Связь между двумя компонентами представлена линией с указанием кардинального числа. Кардинальное число “один" означает, что в этой связи участвует не более одного экземпляра. Кардинальное число “много” (*) совсем не говорит о том, что здесь должно быть сразу несколько экземпляров. Это всего лишь означает, что некоторые архитектурные решения позволяют иметь кардинальное число, превышающее
единицу. Для некоторых связей эта единица является нормой, но в некоторых реализациях допускаются и большие значения. Представленные рисунки не охватывают все существующие архитектурные конфигурации, но они являются основой для дальнейшего обсуждения этой темы.
На рис. 14.1 показаны связи между группами баз данных, базами данных, томами хранимой информации и блоками данных на диске. Стандарт ODMG имеет отношение только к компоненту базы данных. Как отмечалось в главе 2, группа баз данных содержит несколько баз данных. Стандарт ODMG предоставляет возможность присваивать имена объектам внутри одной базы данных. При этом существуют реализации, позволяющие не ограничивать область видимости идентификатора объекта одной базой данных, а охватывать группу баз данных, в то время как поддержка обработки запросов обычно ограничивается только одной базой данных.
Рис. 14.1. Организация и хранение базы данных
Все реализации при хранении информации опираются на использование дисковых томов и блоков, но стандарт ODMG не опускается до подробностей реализации такого низкого уровня, которые обычно не влияют на интерфейс приложения. В большинстве продуктов для хранения одной базы данных предусмотрено несколько томов, хотя в других продуктах для этого отводится только один том. Том состоит из нескольких блоков, в которых хранятся объекты. Объекты большего размера могут занимать несколько блоков.
Процесс приложения может быть включен в один или несколько процессов сервера базы данных. Если приложению нужно получить доступ к нескольким базам данных, то для этого потребуется реализовать протокол двухфазного завершения транзакции.
Программа, обслуживающая базу данных, управляет как кэшем базы данных в процессе сервера базы данных, так и кэшем клиента в процессе приложения клиента. Процессы сервера базы данных и приложения могут быть реализованы на одном и том же или на разных компьютерах. Если они действуют на разных компьютерах (которые используют разные процессоры), программа обслуживания базы данных должна быть готовой к поддержке гетерогенности. Процессы сервера базы данных и приложения клиента могут иметь (а могут и не иметь) несколько потоков выполнения (рис. 14.2). Каждая реализация базы данных имеет уникальную конфигурацию этих архитектурных компонентов. Некоторые реализации обладают определенной гибкостью и поддерживают несколько конфигураций (мы рассмотрим некоторые альтернативные архитектурные решения ниже в этой главе). В верхней части рис. 14.2 показаны архитектурные элементы обработки данных, а в нижней — местоположение этих данных в архитектуре. Главная функция каждой архитектуры — перемещение данных между базой данных, кэшем базы данных и кэшем клиента. В следующих разделах будет уделено внимание каждому из упомянутых здесь компонентов и их взаимосвязям.
Рис. 14.2. Процессы и кэши сервера базы данных и приложения клиента
Организация базы данных
Физически база данных организована так, что ее объекты хранятся на диске. Приложение обычно получает доступ к объектам базы данных на основе определенных логических связей. Как правило, объект является частью нескольких логических объединений, которые формируются либо статически, либо динамически. Отображение объектной модели на физическую организацию базы данных может иметь огромное влияние на общую производительность приложения.
Физическая и логическая организации данных принимают форму многоуровневых иерархий. На рис. 14.1 показана иерархия хранения объектов на физическом уровне, а примеры логической иерархии приводятся ниже в этой главе.
Логическая организация базы данных и доступ к объектам
Приложение обычно получает доступ к базе данных на объектной основе. В главе 6 описаны механизмы, используемые для идентификации объектов: идентификаторы, имена и значения атрибутов. Приложение обычно получает доступ к экземпляру класса т с помощью объектной ссылки (d_Ref<T>).
Приложения также получают групповой доступ к объектам. Во многих продуктах объектных баз данных успешно реализованы механизмы эффективной передачи между клиентом и сервером целых групп объектов. С точки зрения затрат системных ресурсов, обычно эффективнее получить доступ к целой группе объектов, чем нести расходы на передачу “клиент/сервер” каждого объекта в отдельности.
По определению объект коллекции содержит группу элементов. Когда объект коллекции реализуется в приложении, его элементы обычно также реализуются, т.е. создаются их экземпляры (хотя бывают и исключения). Элементы коллекций могут ссылаться на другие объекты. Существуют реализации, которые могут неявно создавать экземпляры адресуемых объектов при активизации объекта коллекции в приложении, или же предусмотренный в реализации механизм может позволить приложению явно создавать экземпляры всех объектов, адресуемых элементами коллекции.
Экземпляры любого класса составляют логическую группу объектов. У приложения может возникнуть необходимость в доступе к каждому экземпляру. Классы в объектной модели организованы иерархически, поэтому доступ ко всем экземплярам класса может подразумевать доступ либо к экземплярам конкретного класса, либо к экземплярам как данного класса, так и всех производных классов. Иерархия классов — это одна логическая группа, которая существует среди экземпляров в базе данных.
Составной объект представляет еще одно логическое группирование объектов, и зачастую он представляет собой многоуровневую иерархию экземпляров. Реализации объектных баз данных, как правило, не обеспечивают прямую поддержку доступа ко всем экземплярам, связанным с составным объектом. Однако в некоторых случаях они предоставляют необходимые низкоуровневые механизмы, чтобы приложение могло в явном виде получить эффективный доступ к подобъектам составного объекта.
Некоторые реализации позволяют приложению получить доступ к конкретному объекту и ко всем объектам, на которые он ссылается. В каких-то случаях приложение может указать конкретное количество уровней разыменования. Такую группу объектов называют замыканием объектов (object closure). Полное замыкание (complete closure), или транзитивное замыкание (transitive closure), состоит из всех экземпляров, которые транзитивно достижимы через ссылки. Другими словами, если экземпляр А ссылается на экземпляр в, в — на С, а с — на D, то полное замыкание экземпляра А должно включать экземпляры А, в, С и D.
Группа объектов может задаваться динамически с помощью запроса, который может учитывать (или не учитывать) статические связи между объектами. Приложение может выполнить OQL-запрос, чтобы вернуть множество ссылок на объекты, которые отвечают заданному булеву предикату. При возврате результата такого запроса реализации могут поддерживать активизацию адресуемых объектов. При таком подходе минимизируется количество необходимых коммуникаций “клиент/сервер”.
Физическая организация хранения объектов
Как показано на рис. 14.1, физическая иерархия хранения объектов существует в среде любой базы данных. Рис. 14.3 иллюстрирует связи между компонентами общего комплекса хранения данных. На самом внешнем уровне иерархии находится группа баз данных, которая содержит три базы данных. Каждая база данных состоит из одного или нескольких сегментов, которые также называются томами. (Термин том (storage volume) обычно употребляется для данных на диске, а сегмент (segment) — для тех же данных, но находящихся в оперативной памяти.) Том, как правило, связывают с одним запоминающим устройством. Он содержит набор блоков, обычно фиксированного размера. На этом рисунке блоки внутри каждого тома показаны в виде прямоугольников с контурами черного цвета. Размер тома и блоков обычно устанавливается администратором базы данных при ее первоначальной инициализации. В блоках тома размещаются объекты, изображенные здесь прямоугольниками с контурами белого цвета. Объекты имеют различный размер и могут занимать несколько блоков. Дисковые передачи данных обычно выполняются целыми блоками. Чем больше размер блока тем больше объектов он может содержать, поэтому увеличение размера блока способствует сокращению дисковых передач, требуемых для доступа к данному количеству объектов.
Группа баз данных
Рис. 14.3. Физическая иерархия хранения объектов
Некоторые реализации используют возможности виртуальной памяти операционной системы и выполняют активизацию, передачу и блокировку объектов постранично. Страница рассматривается как эквивалент блоку или же блок может содержать фиксированное количество страниц. Для упрощения предположим, что блок имеет тот же размер, что и страница в приложении.
Отображение логической организации на физическую
Объектная модель обеспечивает естественное группирование объектов. Во время анализа объектов важно определить логические группы, к которым будут получать доступ транзакции, и понять степень параллелизма, необходимую приложению. Также нужно продумать, какая должна быть гранулярность наложения блокировок. Подобный анализ имеет важное значение для определения того, каким образом объекты будут подвергнуты кластеризации или разделению.
На рис. 14.3 показана физическая иерархия хранения объектов. Чтобы добиться максимальной пропускной способности системы, объекты, совместно используемые в транзакциях, должны быть кластеризованы (объединены), в то время как объекты, к которым, возможно, потребуется организовать параллельный доступ со стороны различных транзакций, следует разделить. Если блокировки накладываются на физическом уровне, то при размещении объектов в иерархии системы хранения должна быть учтена гранулярность выполнения блокировок.
Кластеризация объектов
Кластеризация объектов, которые будут использоваться одной и той же транзакцией, предоставляет много преимуществ. Она минимизирует количество дисковых блоков, содержимое которых должно передаваться между кэш-памятью базы данных и запоминающими устройствами, повышая тем самым ценность использования кэша базы данных. Кластеризацию объектов можно выполнять блоками, сегментами или целыми базами данных. Если блокировка объектов выполняется по блокам, то кластеризация объектов одного и того же блока также сокращает количество блокировок, которые необходимо наложить.
В некоторых реализациях объектных баз данных предусмотрена автоматическая кластеризация. Распространенный метод — кластеризовать все экземпляры одного класса. Элементы коллекции часто кластеризуются ради более быстрого доступа ко всем элементам коллекции. Программа обслуживания базы данных может автоматически кластеризовать взаимосвязанные объекты, поскольку приложению обычно требуется доступ именно к таким объектам. Взаимосвязанные объекты и объекты, созданные в одной и той же транзакции, могут подвергаться кластеризации автоматически. Это нетрудно реализовать, поскольку база данных при первоначальном создании объектов должна решить, где их разместить. Заполнение пустого блока объектами проще и требует менее трудоемкого управления параллелизмом, чем доступ к блокам, к которым возможен доступ со стороны других транзакций. К объектам, созданным в одной транзакции, часто и впоследствии выполняется совместный доступ со стороны других транзакций.
Обычно приложение может также задать, как именно следует кластеризовать объекты. Если существует коллекция объектных ссылок и к адресуемым объектам доступ обычно реализуется с помощью этой коллекции, то адресуемые объекты следует кластеризовать “по соседству" с этой коллекцией. Подобъекты обычно кластеризуются вместе со своим составным объектом. Составной объект может содержать несколько групп подобъектов, но транзакция может получать доступ только к одной из этих групп. Поэтому, возможно, имеет смысл кластеризовать подобъекты внутри группы, но при этом следует отделить каждую группу.
Иногда автоматическая кластеризация, обеспечиваемая конкретной реализацией базы данных, не отвечает “кластеризационным” потребностям приложения, и поэтому ее нужно обойти, используя явную кластеризацию. Обычно это необходимо в случаях, если кластеризуемые объекты требуются различным транзакциям, а наложенная блокировка препятствует параллельному доступу. Определенный в классе d_Object оператор new (в одной из своих форм) как раз обеспечивает возможности кластеризации.
Кластеризация и разделение объектов в нескольких базах данных
В среде с несколькими базами данных необходимо решить, выполнять ли кластеризацию объектов в одной и той же базе данных или поместить объекты в отдельные базы данных. Конфигурация, рассчитанная на несколько баз данных, имеет много преимуществ. Например, разделяя объекты и процессы приложений, можно разместить базы данных либо на одном компьютере, либо на разных. Цель этого — с максимальной эффективностью использовать системные ресурсы в большой мультикомпь- ютерной среде. Разделение объектов на отдельные базы данных позволяет этим базам данных размещаться на различных аппаратных ресурсах. Использование различных дисков для каждой базы данных устраняет “яблоко раздора” среди нескольких приложений, получающих доступ к различным базам данных. Распределение всей обработки на несколько компьютеров позволяет использовать больше процессоров для выполнения трудоемких задач.
Объекты могут требовать обеспечения различных уровней параллелизма, целостности и ошибкоустойчивости. Требования объектов, исполняющих роль ссылочных ресурсов и предназначенных только для чтения, отличаются от требований объектов, которые должны характеризоваться высокой скоростью выполнения операций вставки или значительными параллельными обновлениями. Переменные, которые оказывают влияние на возможности параллелизма, целостности и ошибкоустойчивости базы данных, определяются на уровне базы данных. Базы данных, как правило, могут обеспечить параметры, позволяющие приложению регулировать уровень безотказности и тем самым ускорять выполнение операций за счет ослабления требований к безотказности. Поэтому для обеспечения возможностей соответствующего уровня могут использоваться базы данных с различными операционными характеристиками.
Кластеризация или разделение объектов баз данных могут быть выполнены на основе доступных машинных ресурсов и ожидаемого объема “объектной” нагрузки на каждую базы данных. Обычно более эффективна кластеризация объектов, к которым чаще всего нужен доступ; в этом случае минимизируются коммуникационные расходы, если эти объекты и приложение работают в среде одного компьютера. Если же к некоторому множеству объектов приложение обращается редко, то приложение и объекты — кандидаты для разделения по отдельным машинным ресурсам. Разделение объектов по разным базам данных окажет влияние на приложение в зависимости от степени распределенности обработки запросов и распределенности управления транзакциями, поддерживаемой конкретной реализацией. При разделении объектов на несколько баз данных вам придется учесть расходы на коммуникацию “клиент/сервер”.
Дрхлтвктдра класса клиент/сервер
Как показано на рис. 14.2, большинство реализаций поддерживает архитектуру класса клиент/сервер, которая обеспечивает гибкость использования однокомпьютерной среды или распределения общей нагрузки на несколько компьютеров. Процесс сервера может обеспечить обслуживание одного или нескольких процессов клиента, а клиент может взаимодействовать с несколькими серверами. Один и тот же процесс может играть роль сервера в одном контексте и роль клиента в другом. Часто для клиента и сервера доступны несколько коммуникационных механизмов.
Каждый процесс выполняется на конкретном компьютере. Каждый компьютер имеет вполне конкретную архитектуру процессора, которая часто определяет двоичное представление данных, обрабатываемых на данном компьютере. Размер целого слова и порядок байтов в слове различны для разных процессоров. Процесс клиента и процесс сервера могут выполняться на компьютерах с разными архитектурами процессоров, и иногда это разрешается системой, которая поддерживает гетерогенность архитектуры клиент/сервер. Некоторые ранние реализации совсем не поддерживали такую разнородность, и добавление такой возможности вызывает существенные трудности, хотя и преодолимые в некоторых случаях.
Существуют различия не только между двоичными представлениями объектов в процессорах, но и между компиляторами языка C++, причем даже в одной и той же компьютерной среде могут быть установлены их различные модификации. Из-за этого, например, могут быть различия в компоновке данных-членов. На большинство реализаций существенно влияет компилятор, используемый приложением. Программа обслуживания базы данных обычно имеет несколько частей, которые зависят от выбранного компилятора. Конфигурация продукта зависит от компьютера, операционной системы и компилятора, используемого приложением клиента.
Некоторые компьютерные среды имеют несколько процессоров, и такая организация приобретает все большую популярность. Наличие нескольких процессоров позволяет процессам клиента и сервера одновременно выполняться на одном и том же компьютере. В прошлом некоторые приложения использовали распределенную архитектуру, чтобы воспользоваться преимуществами нескольких однопроцессорных компьютеров. Появление нескольких процессоров в одном компьютере повышает параллелизм выполнения операций без увеличения коммуникационных расходов на экстенты в распределенной среде.
Другая тенденция заключается в использовании нескольких потоков выполнения в одном процессе. Потоки совместно используют адресное пространство одного и того же процесса, благодаря чему можно говорить об эффективном выполнении операций коммуникации и переключений между потоками. Процесс сервера может связать поток с каждым клиентом. Продукты баз данных обладают многопотоковыми серверами уже в течение нескольких лет, и теперь мы убедились в использовании потоков в приложениях клиентов. Любая транзакция связывается с процессом или каждый поток может иметь собственную транзакцию.
На рис. 14.4 показана типичная архитектура класса клиент/сервер, предназначенная для реализации объектной базы данных. Обычно с конкретной базой данных связывается один кэш базы данных, т.е. все процессы сервера, управляющие данной базой данных, используют один и тот же кэш базы данных. (Различные конфигурации серверов баз данных и кэшей рассматриваются ниже.) Программа обслуживания базы данных также поддерживает кэш клиента в процессе приложения и берет на себя ответственность за перемещение объектов между двумя кэшами. Приложению не нужно беспокоиться о выделении и освобождении памяти для объектов, хранимых в базе данных, — это выполняется автоматически программой обслуживания базы данных.
Разные реализации баз данных используют различную гранулярность данных, передаваемых между клиентом и сервером, а также кэш-памятью базы данных и запоминающими устройствами. Между кэшем базы данных и томами диска данные обычно передаются блоками или сегментами. Программа приложения получает логический доступ к данным с гранулярностью, равной одному объекту или группе объектов. В любом варианте архитектуры предусматривается переход от логического доступа (имеется в виду доступ приложения к объектам) к физическому (в виде управления блоками физического диска). Гранулярность данных, передаваемых между процессами клиента и сервера, часто является величиной, производной от гранулярности, используемой в соответствующих кэшах. Одни реализации передают данные порциями, соответствующими нескольким единицам гранулярности, а другие поддерживают только единичную гранулярность. Обычно передача данных выполняется порциями, равными одному объекту, группе объектов или одной странице.
Процесс клиентаЗапросы данных клиента. Кэш клиента1; Реализации д• в кэше клиента■Процесс клиентаЗапросы данных клиента. Кэш клиента1; Реализации д• в кэше клиента■Передачи данныхмежду сервероми клиентомПередачи данныхмежду сервероми клиентомПроцесс сервераПроцесс сервераКэшбазы данныхУправлениекэшем сервераКэшбазы данныхУправлениекэшем сервера
Процесс клиента
Запросы данных клиента
. Кэш клиента1
; Реализации д• в кэше клиента■
Процесс клиента
Запросы данных клиента
. Кэш клиента1
; Реализации д• в кэше клиента■
Передачи данныхмежду сервероми клиентом
Передачи данныхмежду сервероми клиентом
Процесс сервера
Процесс сервера
Кэшбазы данных
Управлениекэшем сервера
Кэшбазы данных
Управлениекэшем сервера
Дисковыепередачи данных
Рис. 14.4. Архитектура класса клиент/сервер объектной базы данных
Кзш базы данных и процесс сервера
За поддержку кэш-памяти базы данных отвечает процесс сервера базы данных. Он управляет транзакциями и осуществляет контроль за параллелизмом. Кроме того, он руководит перемещением данных между кэшем базы данных и томами на диске, а также между кэшем базы данных и процессами клиентов.
Кэш базы данных должен поддерживать информацию, необходимую для обслуживания клиентов, стремящихся получить доступ к базе данных. Сюда входят:
блоки с содержимым базы данных на диске;
информация об индексах, используемая для определения местоположения объектов;
информация о каждой транзакции и клиенте;
информация о блокировках;
информация о регистрации транзакций.
Конфигурации
Процесс сервера базы данных получает прямой доступ к кэшу базы данных. На рис. 14.2 показано, что кардинальное число между кэшем базы данных и процессом сервера базы данных соответствует типу “один-ко-многим". Некоторые реализации обеспечивают единственный процесс сервера, управляющий кэшем базы данных, который локален и закрыт внутри процесса сервера, как показано на рис. 14.5. Процесс сервера базы данных обрабатывает требования нескольких клиентов, желающих получить доступ к одной и той же базе данных. Без поддержки нескольких потоков внутри одного процесса серверу пришлось бы обрабатывать за один раз только по одной заявке от клиента, что ограничило бы уровень параллелизма. Наличие многопотокового сервера могло бы сыграть ключевую роль в среде базы данных. Если некоторая операция ожидает ответа от внешнего ресурса, например от диска или сетевых средств ввода-вывода, то для процесса сервера было бы рационально в это время переключиться на другой поток и продолжить обработку, т.е. потоки могут существенно увеличить загрузку центрального процессора. В некоторых реализациях серверов механизмы работы с потоками “прижились" еще до того, как операционные системы “научились” поддерживать потоки.
Другие реализации позволяют доступ к общему кэшу базы данных со стороны различных процессов сервера (рис. 14.6), часто используя возможности разделяемой памяти, предоставляемые операционной системой. В типичной реализации за каждым процессом сервера базы данных закрепляется некоторая область совместно используемой
памяти базы данных, которая напрямую адресуется всеми процессами серверов базы данных. Когда некоторое приложение открывает базу данных, запускается процесс сервера, который “прикрепляется” к этой области разделяемой памяти. В мультипроцессорных архитектурах каждый процесс сервера может работать с собственным процессором, что позволяет избежать “борьбы” за обладание процессорными ресурсами.
Рис. 14.5. Единственный процесс сервера базы данных, поддерживающий кэш базы данных и обслуживающий несколько клиентов
Рис. 14.6. Несколько серверов баз данных получают доступ к совместно используемому кэшу базы данных, причем один сервер обслуживает одного клиента
При внесении изменений в общий кэш базы данных необходимо осуществлять определенную координацию и управление параллелизмом. Для управления доступом к общему кэшу используются внутренние блокировки, которые обычно называют защелкал™ (latch), чтобы отличать их от блокировок, накладываемых на процессы приложений. Защелки обычно связываются с ресурсами, управляемыми в кэше базы данных. Гранулярность защелок в кэше базы данных влияет на степень параллелизма, которую могут достичь процессы серверов. Единственная защелка, управляющая доступом ко всему кэшу, слишком “сурова” — строго ограничивает степень параллелизма. Уменьшение области управления одной защелки повышает уровень параллелизма.
При использовании архитектуры, показанной на рис. 14.6, каждый процесс приложения клиента связывается с собственным процессом сервера базы данных. Для коммуникации этих двух процессов предусмотрено несколько специальных механизмов. При использовании протокола TCP/IP процессы клиента и сервера могут действовать как на одном, так и на разных компьютерах. При работе на одном компьютере существует ряд других возможностей, повышающих общую эффективность функционирования системы по сравнению с применением протокола TCP/IP. Например, два процесса для передачи данных могли бы использовать общую область памяти.
В некоторых реализациях программу приложения можно непосредственно скомпоновать с библиотеками, содержащими код сервера базы данных, тем самым создав единый процесс приложения (рис. 14.7). Такая организация позволяет реализовать передачу данных между кэшами базы данных и клиента в виде прямого копирования памяти, что резко сокращает коммуникационные расходы на общение клиента с сервером. Однако такая среда базы данных менее безопасна, поскольку приложение может потенциально испортить кэш базы данных. Тем не менее она предоставляет эффективный доступ к кэшу при небольших расходах на коммуникацию клиент/сервер.
Рис. 14.7. К кэшу базы данных получают доступ монолитные процессы
Другой вариант архитектуры позволяет осуществить параллельный доступ к разделяемому кэшу базы данных путем введения дополнительных потоков в модель одного процесса, изображенную на рис. 14.5. Некоторые среды операционных систем позволяют выполнять потоки одного процесса параллельно на нескольких процессорах. При такой организации возможна реализация архитектуры, которая во многом похожа на мультисерверную архитектуру с общим кэшем, изображенную на рис. 14.6. На рис. 14.8 показан односерверный процесс, который содержит отдельный поток для связи с каждым клиентом. По сути, потоки здесь получают доступ к кэшу базы данных в одном процессе, вместо того чтобы несколько процессов получали доступ к некоторой общей области совместно используемой памяти. Этот процесс предоставляет адресное пространство общей виртуальной памяти, разделяемое всеми потоками. Ключевым фактором здесь является относительная эффективность контекстного переключения на уровне потоков, а не на уровне процессов. Переключение между потоками обычно более эффективно, чем переключение между процессами. В этом случае существенно сокращается объем контекстной информации, которую должна изменить операционная система при свершении факта переключения.
Представление данных в каше базы данных
Процесс сервера базы данных считывает и записывает блоки данных между кэшем базы данных и томами диска. Как правило, представление данных в обоих местах идентично. Однако реализации продуктов значительно отличаются одна от другой тем, как перманентные объекты представляются на диске. Приложения клиента могут работать на различных процессорах и использовать различные компиляторы. В гетерогенной среде сервер и клиент обычно не имеют одинакового двоичного представления объектов. Одни реализации поддерживают гетерогенность, а другие — нет или с большими ограничениями.
Рис. 14.8. Архитектура многопотокового сервера
Сколько фирм-производителей, практически столько же и вариантов представления данных. Представление данных во вторичной памяти часто является следствием методов, применяемых конкретной реализацией в отношении гетерогенности среды клиент/сервер. В настоящее время используются следующие подходы.
В строго гомогенной (однородной) среде клиент/сервер и реализации базы данных используется двоичное представление конкретного процессора и конкретного компилятора.
Объект хранится в двоичном представлении, используемом сервером, а затем между гетерогенными процессорными средами на двоичном уровне выполняются соответствующие преобразования.
Представление объекта ориентировано на процессор приложения клиента, в котором первоначально был создан объект. В этом случае сервер базы данных строго управляет бинарными страницами без какой бы то ни было интерпретации их содержимого. Двоичные преобразования данных между гетерогенными процессорными средами выполняются при активизации страницы в приложении клиента.
Объекты хранятся в канонической форме, не зависящей от используемых процессоров, а двоичные представления конкретных клиентов устанавливаются при передаче объектов между средами клиента и сервера.
Описанные выше представления обеспечивают различные уровни гетерогенности.
Одни реализации оснащены серверами, которые “осознанно” работают с данными на уровне объектов, а другие — нет. Обычно, если сервер может обрабатывать отдельные объекты в кэше базы данных, значит, он способен выполнять такие задачи, как блокировка, обработка запросов и передача данных “клиент/сервер” на уровне объектов. Если запросы могут выполняться в сервере базы данных, то клиенту нужно передать только окончательный результат запроса. Реализации, в которых серверы не могут работать на объектном уровне, обычно выполняют подобные действия, опираясь только на блоки данных. Запросы в этом случае обрабатываются в процессе клиента, потенциально требуя, чтобы клиенту передавался больший объем данных, нежели при серверной обработке запросов.
Управление нараллелнзмпм
Кэш базы данных обслуживает всё приложения, получающие доступ к базе данных. Кэш базы данных должен содержать информацию о выполнении каждой транзакции. Он обеспечивает координацию всех процессов и должен поддерживать информацию о блокировках на уровне объекта, блока, сегмента или базы данных. Неко-
торые реализации поддерживают блокировку на уровне класса или иерархии классов. При блокировке на уровне иерархии классов неявно блокируются все экземпляры заданного класса и экземпляры всех классов, выведенных из этого класса.
Объекты размещаются как в физической, так и в логической иерархиях, которые определяют различные ортогональные группирования объектов на разных уровнях гранулярности. Блокирование обычно выполняется на одном или нескольких уровнях внутри одной из этих иерархий. Некоторые реализации устанавливают блокировки на уровне гранулярности, который выше, чем у приложения, получающего доступ к данным. Размещение данных внутри иерархий особенно важно, если нужно найти баланс между степенью параллелизма и затратами системных ресурсов на блокировку и защиту тупиковых ситуаций.
Для координации блокирования на различных уровнях иерархии используются преднамеренные блокировки (intention locks). Такие блокировки обычно накладываются только программой обслуживания базы данных и используются для неявного блокирования узлов в поддереве иерархии. Когда некоторая транзакция пытается наложить блокировку на некоторый узел в иерархии, соответствующая преднамеренная блокировка должна быть сначала наложена на родительский узел. Это правило применяется рекурсивно вплоть до корня дерева. Попытки наложить преднамеренные блокировки начинаются с корня и продолжаются вниз по иерархии до тех пор, пока не будет достигнут узел, к которому осуществляется доступ. Преднамеренные блокировки могут минимизировать вероятность возникновения конфликтов (при наложении блокировок) и тупиковых ситуаций.
Блокирование при высокой гранулярности обеспечивает неявную блокировку всех элементов, “видимых’" при более мелкой гранулярности. Следовательно, это уменьшает количество блокировок, накладываемых на объекты такой гранулярности. Но сие также означает, что все эти объекты блокируются даже в том случае, когда это не нужно приложению. Чтобы максимально увеличить степень параллелизма, блокировать нужно только то, к чему приложение получает доступ.
Некоторые реализации объектных баз данных выполняют блокирование постранично, что имеет как свои преимущества, так и недостатки. При блокировании страницы блокируются все объекты, находящиеся на ней. Если приложение получает доступ к большинству из этих объектов или они не нужны другим приложениям, такая организация прекрасно работает. И в самом деле, она более эффективна с точки зрения “штаба” управления блокировками, поскольку для блокирования всех объектов на странице требуется только одна блокировка. Но при этом уровень параллелизма может уменьшиться. Если две или больше транзакций попытаются обновить различные объекты, расположенные на одной и той же странице, то одновременно может быть выполнена только одна транзакция. Но проблема параллелизма приобретает еще большую остроту, когда блокировка выполняется с дискретностью сегмента или целой базы данных.
Сервер базы данных должен обладать способностью обнаруживать тупиковые ситуации, которые могут возникать тогда, когда на транзакции накладывается несколько несовместимых блокировок. Предположим, что транзакции А и В дождались возникновения блокировочных конфликтов. Рассмотрим следующее развитие событий.
Транзакция А накладывает монопольную блокировку или блокировку без взаимного доступа (exclusive lock) на элемент 1.
Транзакция в накладывает монопольную блокировку на элемент 2.
Транзакция А делает попытку наложить блокировку на элемент 2. В этот момент возникает конфликт блокировок, поскольку транзакция А хочет получить доступ к элементу, заблокированному транзакцией в. Это обычный случай, когда транзакции параллельно получают доступ к данным. Однако рассмотрим следующий этап.
Транзакция В пытается наложить блокировку на элемент 1.
Такое развитие событий и приводит к тупиковой ситуации. Транзакция А ожидает, пока транзакция В снимет блокировку, причем в то же самое время, когда В ожидает, пока А снимет блокировку. Ни одна из транзакций не может продолжиться. Если сервер базы данных не обнаружит эту ситуацию, транзакции Айв будут вечно находиться в состоянии ожидания. Чтобы одна из транзакций могла продолжить выполнение, другая должна быть прервана.
Кэш и процесс приложения клиента
Программа обслуживания базы данных автоматически поддерживает кэш клиента для каждого процесса приложения. При доступе к перманентным С++-объектам они помещаются в кэш клиента после преобразования в соответствующее представление (диктуемое процессором и компилятором). Их представление совпадает с представлением других транзитных объектов в приложении. Если объект уже находится в кэше, программа базы данных просто возвращает ссылку на этот объект. На основе информации об обращениях приложения к функции mark_modified программе базы данных известно, какие объекты модифицируются. Когда приложение завершает транзакцию, модифицированные объекты автоматически записываются снова в базу данных. При этом приложению не нужно беспокоиться относительно преобразования экземпляров C++ в кэш-представление приложения при чтении, а также обратного преобразования объектов при записи в базу данных по завершении транзакции. Такая организация значительно сокращает объем работ для разработчика приложения.
Управление кэшем клиента уникально для объектных баз данных. Реляционные, иерархические и сетевые базы данных не занимаются кэшем приложения. Кэш клиента — это один из компонентов в объектной базе данных, который обеспечивает ее прозрачность. Именно свойство прозрачности создает впечатление полного исчезновения базы данных. С точки зрения приложения кажется, что база данных — это просто расширение виртуальной памяти приложения. Некоторые же базы данных использовали виртуальную память в своей архитектуре. Координация действий с подсистемой виртуальной памяти операционной системы создает бесшовный интерфейс для работы с базой данных.
Конфигурации
Даже если клиент связывается с несколькими базами данных, он выполняет одну транзакцию, которая включает обновление объектов, расположенных в одной или во всех связанных базах данных. Процесс приложения клиента, показанный на рис. 14.9, имеет собственный закрытый кэш клиента, расположенный в его адресном пространстве. Он содержит объекты, которые были считаны из одной или нескольких баз данных.
Некоторые архитектуры приложений требуют, чтобы кэш совместно использовался несколькими процессами приложений. Конкретные реализации объектных баз данных позволяют единый кэш клиента разделять между несколькими процессами приложений, как показано на рис. 14.10. Обратите внимание, что архитектура такого клиента, по сути, аналогична мультисерверной архитектуре с разделяемым кэшем (см. рис. 14.6). Очевидно, для того чтобы процессы клиентов могли безопасно модифицировать разделяемые ресурсы кэша, должны быть выполнены определенные координационные мероприятия.
Рис. 14.10. Кэш клиента, совместно используемый несколькими процессами
Подобно тому, как потоки используются в архитектурах серверов баз данных, они применяются и в приложениях клиентов. Фирмы-изготовители объектных баз данных только недавно начали заниматься поддержкой “потоковых” клиентов (рис. 14.11). Большой круг в центре рисунка означает процесс приложения, который связан с двумя базами данных, показанными справа. Два процесса (слева) — это реальные клиенты процесса приложения (в центре). Процесс приложения имеет поток, соответствующий каждому клиенту, а также кэш клиентов, содержащий объекты, считываемые из базы данных.
Рис. 14.11. Многопотоковое приложение клиентов
Приложения клиентов организуют потоки по разным причинам. Процесс объектного сервера может использовать объектную базу данных для обеспечения перманентности объектов, которыми он управляет. Такой процесс является клиентом сервера базы данных, но он также действует как объектный сервер для других клиентов. Для процесса объектного сервера очень удобно иметь отдельный поток и модуль транзакций для каждого клиента. Это обычный сценарий для объектных серверов, действующих в среде CORBA.
После того как фирмы-производители начали поддерживать потоковые процессы клиентов, возникли новые проблемы. Исторически сложилось так, что для одного процесса приложения клиента был допустим только один сеанс транзакции. Для некоторых приложений, использующих потоки, такая организация могла быть вполне адекватной. Но если приложение действует в качестве сервера для собственного набора клиентских процессов, не понадобится ли каждому из этих процессов собственная транзакция? Если клиенты выполняют многошаговые операции и предполагают, что они изолированы от действий других клиентов, го клиенту базы данных потребуется иметь несколько параллельных транзакций: по одной на каждого клиента. Поэтому сервер базы данных должен допускать инициализацию нескольких сеансов транзакций из одного процесса клиента. Фирмы-производители только совсем недавно начали поддерживать такую возможность.
Выделение памяти в каше
Когда приложение пытается получить доступ к объекту, которого нет в кэше, формируется запрос к серверу. Такая ситуация называется дефектом объекта или дефектом кэша. Сервер возвратит этот объект, возможно, “в компании” с другими объектами. Гранулярность, с которой передаются и активизируются объекты в кэше клиента, прозрачна (т.е. не видна) для приложения.
Одни реализации управляют своим кэшем на уровне объектов и могут передавать данные порциями в один объект. Другие же используют стандартную область кучи с помощью С++-операторов new и delete. А есть и такие, которые в собственной отдельной памяти кэша клиента выделяют некоторую область для индивидуальных объектов.
Существуют реализации, которые управляют кэшем клиента на физическом уровне и возврашают объекты постранично. Такая страница соответствует странице операционной системы. Размер блока либо равен размеру страницы, либо содержит несколько страниц.
Выкачка обЪЕктов
Некоторые реализации баз данных или среды приложений ограничивают размер кэша. После того как он заполнен объектами, при попытке получить доступ еще к одному объекту обычно генерируется исключительная ситуация. Но благодаря выкачке объектов (object swapping) разрешается обработка большего числа объектов, чем физически может вместить кэш. Для вывода объектов из кэша (в целях освобождения места для новых объектов) обычно используется схема вытеснения по давности использования (least recently used — LRU). Поэтому виртуальный размер кэша оказывается больше его реального размера, что позволяет приложению манипулировать большим количеством объектов, чем может поместиться в кэше максимального размера. Для реализаций, управляющих кэшем на уровне страниц, такой механизм управления также может действовать на постраничной основе.
Реализации, которые поддерживают выкачку объектов, с каждым объектом связывают двоичный признак (флаг), означающий, что данный объект приколот (pinned), т.е. закреплен в памяти. Для прикалывания (закрепления) или откалывания (открепления) объекта предусмотрены специальные операции. Приколотые объекты нельзя вывести из кэша, в то время как неприколотые — первые кандидаты на “вылет". Реализации баз данных часто по умолчанию устанавливают “закрепленное” состояние для всех объектов, которое может переопределить приложение. И в самом деле.хтолько приложение вправе решать (на основании требований к производительности), какие объекты сделать кандидатами на вывод из кэша. Поскольку выкачанный объект нужно повторно прочитать из диска или сервера, доступ к такому объекту занимает больше времени, чем к кэш-резидентному объекту. Объекты, требующие быстрого доступа, должны быть закреплены в кэше.
При использовании некоторых реализаций приложение не может полагаться на то, что во время транзакции объект будет иметь прежний адрес виртуальной памяти. Выкачанный ранее объект при его повторной активизации может быть помещен в другую область памяти. Поскольку некоторые реализации позволяют во время транзакции совершать выкачку или подкачку объектов, приложения должны быть осторожны при использовании указателей на объекты. В случае выкачки или подкачки объекта значение указателя уже не будет действительным. В таких средах строгое использование объектной ссылки (d_Ref<T>) изолирует приложение от этих адресных изменений.
Перетасовка объектов
Некоторые реализации объектных баз данных во время транзакции перемещают объекты в кэше, чтобы уменьшить его фрагментацию, тем самым увеличивая объем памяти для новых объектов, поступающих в кэш. В таких средах приложениям не следует полагаться на адрес объекта, поскольку он может измениться без какого-либо явного уведомления приложения. И снова-таки, использование объектных ссылок d_Ref<T> изолирует приложение от подобных перемещений. Однако реализация базы данных не должна перемещать объекты, на которые все еще ссылается какая-нибудь функция- член, поскольку она зависит от адреса объекта, содержащегося в указателе this.
Согласованность кашей
Любой объект может находиться более чем в одном кэше. Он может быть в кэше базы данных, а также в нескольких кэшах клиентов. В результате возникает проблема согласованности кэшей (cache coherency). Максимум, что в состоянии сделать один клиент, — он может наложить монопольную блокировку' записи на нужный объект и модифицировать его. Однако после того как объект будет модифицирован в кэше клиента, уже нельзя говорить о синхронизации с его копией, находящейся в кэше базы данных. Когда клиент завершит транзакцию, кэш базы данных будет содержать объект в новом модифицированном состоянии. Но другие приложения, возможно, успели получить доступ к этому объекту (без наложения блокировки чтения), когда он находился в предыдущем состоянии. Многие реализации обеспечивают средства обновления состояния объекта по завершении его модификации некоторой транзакцией.
Проблема согласованности кэшей также имеет место при обработке запросов. Предположим, что приложение получило доступ к нескольким объектам и модифицировало их. После этого объекты находятся в кэше клиента в модифицированном состоянии, но кэш базы данных по-прежнему “довольствуется” исходным состоянием этих объектов. Теперь допустим, что приложение затем выполняет запрос к базе данных, который включает модифицированные объекты. Если этот запрос выполняется на стороне сервера, будет учтено старое состояние объектов, а если на стороне клиента — новое модифицированное состояние. Результат выполнения запроса будет зависеть от того, где происходит обработка запроса.
Одни реализации баз данных предпочитают, чтобы результат запроса был основан на состоянии объектов до модификации. Для других обязательно использование текущего состояния. Реализации, выполняющие обработку запросов в сервере, иногда предоставляют средство, которое перед выполнением запроса записывает модифицированные объекты обратно в кэш базы данных. Такой подход позволяет использовать при выполнении запроса текущее представление приложения о состоянии объектов.
Резюме
Между доступными на рынке реализациями объектных баз данных существует много различий. До появления спецификации общего интерфейса приложения, разработанного консорциумом ODMG, каждая реализация базы данных также имела “свой" (отличный от других) интерфейс программирования. Эти интерфейсы продолжат свое существование и в “эру" доступности интерфейсов ODMG. Фирмы- производители, возможно, будут поддерживать собственные интерфейсы до тех пор, пока на них будет спрос. Интерфейс ODMG не настаивает на использовании какой- то конкретной архитектуры в своей реализации, поэтому фирмы-производители, скорее всего, оставят свои прежние архитектурные решения или внесут лишь минимальные изменения, чтобы улучшить поддержку интерфейсов ODMG.
При сравнении продуктов конкурирующих фирм-производителей, в плане применения к конкретному приложению, главное внимание, как правило, обращается на используемые архитектурные решения. Не секрет, что любые архитектурные решения имеют как сильные, так и слабые места. Обычно производители акцентируют внимание на преимуществах своих продуктов и недостатках продуктов конкурентов. Поэтому для покупателей баз данных важно рассмотреть все альтернативы и взвесить, насколько характеристики каждого предлагаемого продукта отвечают конкретным требованиям их приложения.
Глава 15
Производительность
На общую производительность приложения, работающего с системой управления базой данных, оказывает влияние множество факторов. Качество использования вычислительных ресурсов существенно зависит от архитектурного решения, положенного в основу системы управления базой данных. В разделе 15.1 подробно рассматривается, как именно архитектурные компоненты влияют на производительность приложения.
Для любого приложения важно эффективно использовать возможности базы данных. Объектные и реляционные базы данных имеют сильные стороны, поэтому важно понимать требования, предъявляемые к ним со стороны приложения, и четко представлять, насколько каждая технология построения базы данных отвечает этим требованиям. В том, какая технология базы данных будет работать лучше, определяющую роль играют модель данных приложения и транзакции, обрабатывающие эти данные.
Оценить ожидаемую производительность данного набора транзакций, баз данных и вычислительных сред позволяют эталонные тесты производительности. Несмотря на существование обобщенных тестов, они редко охватывают весь спектр возможных требований со стороны приложений. Поэтому разработчики систем при принятии важных решений могут учитывать результаты, полученные при проведении как общих тестов, так и тестов, специфических для данного приложения. Раздел 15.2 посвящен вопросам тестирования и мониторинга производительности.
Эталонные тесты способны помочь в определении того, можно ли достичь нужного уровня производительности и будет ли работа базы данных рациональной и продуктивной. Анализ некоторых внутренних операционных характеристик может выявить неэффективное использование базы данных и предложить способы изменения параметров конфигурации и их настройки. Те же показатели производительности могут указать направление реструктуризации приложения, чтобы оно более эффективно взаимодействовало с базой данных. Ведь даже небольшие изменения в приложении могут серьезно повлиять на его производительность.
Архитектура баз данных
Производительность приложения определяется архитектурой базы данных, ресурсами компьютера и “способом существования” приложения в среде клиент/сервер. Программа обслуживания базы данных должна работать в разных компьютерных сре-
дах, и эффективное использование физических ресурсов позволит достичь оптимальной производительности. Под физическими ресурсами понимаются память, диски, пропускная способность сети, процессоры и пр.
Рассмотрим типичный вариант архитектуры объектной базы данных, предназначенный для работы в среде клиент/сервер (рис. 15.1). Предположим, что приложению клиента нужен доступ к некоторому объекту базы данных (1). Допустим, что этого объекта еще нет ни в кэше клиента, ни в кэше сервера. Чтобы отыскать объект на диске, процесс сервера базы данных должен использовать объектный идентификатор. Затем сервер базы данных считывает этот объект с диска (5) в кэш базы данных (4), посылает данные из сервера в процесс клиента (3) и реализует этот объект в кэше клиента (2). Помимо расходов на управление параллелизмом и поиск объекта на диске, потребуется выполнить большой объем работы, связанной с передачей данных: между диском и кэшем базы данных, а также между процессами сервера и клиента. Затраты системных ресурсов незначительны, если объект уже находится в кэше клиента. Если объекта нет в кэше клиента, но он присутствует в кэше базы данных, расходов на его передачу требуется меньше, чем в случае, когда он хранится только на диске. Скорость, с которой база данных разыменовывает объектную ссылку и “добывает" сам объект, зависит от местоположения объекта и количества передач, которое необходимо для этого осуществить. Реализации баз данных, поддерживающие гетерогенные среды, несут дополнительные расходы на преобразование объекта в двоичное представление, требуемое в кэше клиента. Ниже мы рассмотрим, как использование кэша и расходы на передачу данных влияют на общую производительность базы данных.
Процесс клиентаФЗапросы данных клиентаКэш клиента©Реализациив кэше клиента у >(<Процесс клиентаФЗапросы данных клиентаКэш клиента©Реализациив кэше клиента у >(<Процесс сервераПроцесс сервера©Передачи данных между сервером и клиентом©Передачи данных между сервером и клиентомКэшбазы данных©Управлениекэшем сервераКэшбазы данных©Управлениекэшем сервера
Процесс клиента
Ф
Запросы данных клиента
Кэш клиента©Реализациив кэше клиента у >(<
Процесс клиента
Ф
Запросы данных клиента
Кэш клиента©Реализациив кэше клиента у >(<
Процесс сервера
Процесс сервера
©
Передачи данных между сервером и клиентом
©
Передачи данных между сервером и клиентом
Кэшбазы данных©Управлениекэшем сервера
Кэшбазы данных©Управлениекэшем сервера
Дисковыепередачи данных
©
Рис. 15.1. Типичный вариант архитектуры объектной базы данных, предназначенный для работы в среде клиент/сервер
Эффективное использование кзшей
Успешное получение доступа к объекту в каком-либо кэше называют кэш- попаданием (cache hit). Если объекта в кэше не оказалось, такая ситуация именуется кэш-неудачей (cache miss). При увеличении количества кэш-попаданий увеличивается производительность приложения. Если объект уже находится в кэше клиента, нет необходимости в дисковых передачах или передачах “клиент/сервер”. В этом случае мы имеем дело с попаданием в кэш клиента (client cache hit). Если объекта нет в кэше клиента, но он присутствует в кэше базы данных, получаем ситуацию попадания в кэш базы данных (database cache hit). Из кэша базы данных в кэш клиента можно передать объект без затрат на дисковые передачи.
Кэши клиентов и базы данных находятся в памяти. Различают три формы памяти: память кэшей клиентов и базы данных, виртуальную и физическую память. В идеальном варианте мы хотели бы всегда иметь физическую память в объеме, достаточном для удовлетворения требований любых приложений, но действительность редко оправдывает наши ожидания. Виртуальная память, предоставляемая операционной системой, является эффективным инструментом обработки пиковых (максимальных) потребностей в памяти. Память для кэшей клиентов и базы данных отводится на страницах виртуальной памяти, которые в любой момент могут (или не могут) быть преобразованы в физические страницы памяти. Основная цель этих кэшей — избежать расходов на дисковые передачи.
Некоторые реализации объектных баз данных, опираясь на требования, предъявляемые приложением, могут выкачивать на диск объекты, расположенные в кэше клиентов или базы данных. Часто программа обслуживания базы данных “лучше знает” (чем операционная система), какие объекты наименее критичны и могут выступать в качестве “самых перспективных” кандидатов на “выдворение” из памяти.
В зависимости от используемого архитектурного решения, кэши базы данных и клиентов могут содержать “посторонние” объекты. Передачи между кэшем базы данных и диском обычно выполняются блоками. Даже когда приложение получает доступ только к одному объекту, в кэше базы данных может разместиться целый блок данных. Если объекты, которые понадобились всем приложениям, разбросаны по многочисленным дисковым блокам, в кэш базы данных будет затребовано больше блоков. Чтобы максимально повысить эффективность использования кэшей, следует минимизировать количество считываемых блоков за счет кластеризации объектов, к которым часто осуществляется доступ в одной и той же транзакции. При этом также минимизируется число дисковых передач.
Те же принципы применимы и к реализациям, которые передают в кэш клиента данные целыми физическими страницами или блоками. В результате, в кэше клиента выделяется память для объектов, которые не нужны приложению, и выделенная для посторонних объектов память становится недоступной для других объектов. Существуют реализации баз данных, которые передают приложению только необходимые ему объекты.
Следовательно, при оценке архитектуры объектной базы данных важно поинтересоваться характером заполнения кэша клиента: содержит ли он целые блоки (страницы) объектов или только объекты, необходимые приложению. В первом из двух вариантов для максимально эффективного использования кэша клиента нельзя обойтись без кластеризации объектов.
5.1.1.1. Холодоыи о горячий кэши
Холодный кэш (cold cache) не содержит никаких объектов. При первоначальном доступе к базе данных со стороны приложения кэш базы данных не содержит ни одного объекта. По мере выполнения обращений к базе данных, ее кэш постепенно “захватывает” некоторый набор блоков с объектами. После того как кэш наполнится объектами “до отказа” и при этом потребуются новые блоки с объектами, начинает работать схема вытеснения по давности использования, т.е. блоки-“долгожители” становятся кандидатами на выкачку, чтобы освободить место новым блокам. Если блок- кандидат содержит данные, которые были только прочитаны приложениями, он может быть “списан”. Если будет сделан еще один запрос на этот блок, исходное содержимое этого блока может быть просто считано из базы данных. Если же блок-кандидат содержит модифицированные данные, база данных должна сохранить этот модифицированный блок в области регистрации транзакций или области выкачки данных.
Горячий кэш (hot cache) содержит множество объектов, к которым недавно был получен доступ. Горячий кэш клиента содержит рабочий набор объектов, к которым приложение проявило интерес. Горячий кэш базы данных содержит данные, к которым получали доступ все приложения, работающие с этой базой данных. Горячий кэш может уже содержать нужные объекты, и в этом случае необходимость в передаче данных отпадает. Но такое “везение” наблюдается не всегда. Цель, к которой следует стремиться, — иметь кэш, который будет всегда содержать нужные объекты.
5.1.1.2. Поддержка адекватного размера кзша
Приложение должно уметь управлять размером кэша. Различным приложениям требуются разные размеры кэшей. В данном случае конкретное приложение должно иметь возможность устанавливать подходящий размер кэша. Во многих реализациях баз данных предусмотрены параметры конфигурации, которые позволяют приложению управлять начальным и максимальным размерами кэша, а также размерами инкремента для кэшей клиента и базы данных.
5.1.1.3. Кэширование объектов между транзакциями
Большинство реализаций объектных баз данных сохраняет содержимое кэша базы данных за границами транзакций. Многие реализации позволяют приложению сохранять за границами транзакций и содержимое кэша клиента. Функция commit ODMG- класса d_Transaction снимает все блокировки и удаляет все объекты из кэша. Следующая транзакция начинает “с чистого листа”, т.е. с холодного кэша клиента. Есть и другой вариант — вызов функции checkpoint, которая выполняет завершающие операции, но оставляет все блокировки и объекты в кэше. Этот метод позволяет сохранить горячий кэш клиента за границами транзакций.
Некоторые фирмы-производители предоставляют дополнительные возможности сохранения кэшей, которые позволяют приложению указать, какие объекты в кэше должны быть оставлены за границами транзакций. Такой подход позволяет приложению избежать расходов на восстановление объектов из базы данных в начале каждой транзакции.
Описанная организация особенно полезна для приложения, которое требует, чтобы в начале каждой последующей транзакции в кэше находился один и тот же (как правило, большой) набор объектов, например ссылок. Сохранение этих объектов между транзакциями ликвидирует расходы на активизацию и дезактивизацию объектов с каждой последующей транзакцией. Приложение также может создавать или активизировать во время транзакции другие объекты, в сохранении которых между транзакциями нет никакой необходимости. Для таких приложений важна способность сохранять нужное подмножество объектов.
5.1.1.4. Переменные для мониторинга эффективности иснользования кзша
Некоторые реализации предоставляют ряд переменных для контроля за использованием кэшей базы данных и клиента. Состав этих переменных зависит от определенных аспектов архитектуры, используемых фирмой-производителем (например, гранулярность, с которой данные обрабатываются в кэше — по одному объекту или постранично).
Большинство из перечисленных ниже переменных предоставляется для обоих кэшей: базы данных и клиента. Параметры кэша базы данных чаще всего опираются на блоки. В. основе переменных кэша клиента лежат либо объекты, либо блоки, в зависимости от гранулярности обработки данных в этом кэше.
Степень использования кэша (в процентах).
Коэффициент кэш-попаданий и коэффициент кэш-неудач (приложение должно контролировать относительные значения этих параметров).
Количество объектов или блоков, присутствующих в данный момент в кэше.
Количество объектов или блоков, считанных в кэш.
Количество объектов или блоков, записанных из кэша.
Количество объектов или блоков в кэше, которые отмечены для обновления.
Количество объектов или блоков, выкачанных из кэша.
Количество свободных или занятых блоков (в кэше базы данных).
Количество блокировок для каждого режима блокировки.
Количество свободных или занятых байтов в кэше.
Количество объектов в кэше клиента включает вновь созданные объекты, которые, возможно, не были считаны из базы данных. Количество объектов или блоков в кэше может быть также меньше количества объектов или блоков, считанных в кэш, поскольку некоторые объекты или блоки могли быть выкачаны из кэша.
Расходы на дисковые передачи
Любая реализация использует диски для хранения содержимого базы данных, информации о регистрации, а также объектов или блоков, выкачанных из кэшей. Содержимое базы данных включает объекты приложения, индексы и информацию о схеме, хранимую в словаре данных.
Оптимальный вариант хранения данных подразумевает использование нескольких дисковых устройств. Это позволяет повысить производительность благодаря параллельному выполнению операций ввода-вывода. Некоторые операционные системы поддерживают работу с дисковыми массивами. При такой организации несколько независимых дисков объединяются в массив, который для приложения представляет собой один высокопроизводительный диск большой емкости. При записи данные расщепляются на небольшие порции, называемые полоскал™ (disk striping), и распределяются по всем дискам. Для надежности содержимое базы данных и информация о регистрации транзакций должны быть размещены на отдельных дисках. Ведь невозможно восстановить содержимое базы данных с неисправного диска, если вся информация находится на одном диске.
Область подкачки данных, принадлежащая операционной системе, должна быть также размещена отдельно от дисков, используемых базой данных. Многим приложениям, предназначенным для работы с объектной базой данных, приходится управлять большим количеством объектов в кэше клиента, и в этом случае наиболее вероятной будет постраничная организация виртуальной памяти. Использование отдельного диска для подкачки страниц поможет минимизировать возможность возникновения конфликтных ситуаций при обращении к дискам.
Большинство UNIX-реализаций баз данных позволяют приложению выбрать место хранения содержимого базы данных: в файловой системе или непосредственно на исходном устройстве. Во многих случаях разделение исходного диска более эффективно, чем использование файла операционной системы. При работе с файловой системой UNIX содержимое базы данных сначала переносится с диска в буферные пулы операционной системы, а затем копируется в кэш базы данных. Использование исходных устройств позволяет избежать двойного копирования. Благодаря поддержке буферных пулов операционной системой, к файлу могут получить доступ сразу несколько приложений. Но в среде базы данных обычно существует только один кэш базы данных, из которого доступ к данным может получить один или несколько процессов сервера
базы данных. Недостаток использования исходного устройства — выделение фиксированного объема дискового пространства, в то время как файловая система UNIX предлагает гибкость динамического изменения размеров файлов.
Один из способов оптимизации производительности —- определение оптимального размера блока для дисковых передач. Большинство реализаций поддерживает размер блока, который кратен размеру блока, используемому операционной системой, а именно в пределах 4—16 Кбайт. От размера блока зависит количество объектов, которое он может содержать. Следовательно, размер и количество объектов, которые физически кластеризованы и к которым выполняется совместный доступ, помогают определить наиболее подходящий размер блока для данного приложения.
Гранулярность регистрации транзакций различна для разных приложений. Часто регистрация выполняется по объектам, страницам или блокам. Количество объектов, модифицированных во время транзакции, влияет на объем данных по регистрации активности, как и количество параллельных транзакций. Транзакции, которые модифицируют большое число объектов, обычно требуют больше буферов для регистрации транзакций, чтобы можно было уменьшить количество дисковых передач.
Настраиваемые параметры часто используются для управления следующими значениями.
Признак подключения или отключения регистрации транзакций.
Размер буфера регистрации транзакций.
Количество блоков в буферах регистрации транзакций.
Начальный размер сегмента регистрации транзакций на диске.
Размер инкремента сегмента регистрации транзакций.
Передачи “клиенш/сервер”
Расходы на операции передачи “клиент/сервер” влияют на общую производительность системы управления базой данных, а пропускная способность коммуникаций между процессами сервера и клиента определяет скорость передачи данных.
Гранулярность, используемая приложением для доступа к данным, также оказывает влияние на общую производительность. Обычно более эффективно передавать в одном запросе много объектов, чем выполнять много запросов, передавая по одному объекту. Еще один фактор — это соотношение между гранулярностью, с которой данные в действительности передаются приложению, и требуемой гранулярностью. Реализации могут передавать объект или группу объектов на логическом уровне, что означает передачу страницы или сегмента на физическом уровне.
Другим фактором является соотношение между количеством объектов, требуемых приложением, и количеством передаваемых объектов. Реализации, которые выполняют передачи на уровне страниц или сегментов, заполняют кэш приложения всеми объектами из физического блока, даже если приложению нужен только один объект. В таких системах важную роль играет кластеризация, позволяющая как наиболее эффективно использовать кэш, так и минимизировать расходы на сетевые передачи данных.
Механизм коммуннкацни между клиеншнм и сервером
Процессы клиента и сервера могут выполняться на одном или разных компьютерах. Любая среда часто имеет ряд средств коммуникации между процессами (interprocess communication — IPC) и сетевых возможностей, которые используются для связи между процессами клиента и сервера. Когда два процесса выполняются на разных компьютерах, часто используется протокол TCP/IP, хотя ничто не мешает использованию этого протокола при работе обоих процессов на одной машине. Некото-
рые реализации баз данных ради эффективного перемещения данных между процессами используют общую память. Некоторые несерверные реализации для доступа к удаленной базе данных используют (посредством файловой системы) протокол NFS (Network File System), который обеспечивает доступ к распределенным по сети файлам, как если бы они находились на локальном диске.
Настройка и контроль переменнык
В некоторых реализациях предусмотрены переменные для контроля и настройки трафика клиент/сервер. Сервер базы данных имеет специальные буферы для хранения сообщений, получаемых и передаваемых клиентами. Некоторые реализации позволяют приложению управлять размером и количеством буферов сообщений в кэше базы данных. Переменные сервера можно использовать для уведомления о количестве объектов или байтов, считанных или записанных для клиентов; секунд, затраченных на чтение и запись; и запросов, поступивших от клиентов. Переменные клиентов могут сообщить количество передач серверу (и от него), число байтов, прочитанных или записанных на сервер, и секунд, затраченных на чтение и запись на сервер. Эти данные можно использовать для оценки того, является ли коммуникация клиент/сервер “узким местом” в данной системе.
Параллелизм
Если к базе данных получают доступ одновременно несколько приложений, важно повысить до максимума уровень параллелизма. Для координации доступа к базе данных накладываются блокировки, и способ их наложения как раз и влияет на степень параллелизма. В идеальном варианте должны блокироваться только нужные объекты. Если блокируется доступ к другим объектам, уровень параллелизма понижается.
Для настройки уровня параллелизма можно использовать следующие параметры.
Признак включения и отключения настройки блокирования.
Промежуток времени до окончания ожидания блокировки.
Максимальное количество параллельных транзакций.
Если к базе данных доступ получает только одно приложение, некоторые реализации позволяют ему совсем отключить настройку блокирования. При таком подходе сокращаются расходы на наложение блокировок и поддержку их состояния в кэше базы данных.
Для контроля за текущим уровнем параллелизма используются параметры, значения которых перечислены ниже.
Количество заблокированных страниц или объектов для каждого типа блокировки.
Количество клиентов, которые связаны с сервером.
Количество активных транзакций.
Количество начатых, завершенных или прерванных транзакций.
Количество возникших тупиковых ситуаций.
Количество тайм-аутов в ожидании блокировок.
Количество секунд, затраченных клиентом в ожидании блокировки.
Количество зафиксированных ожиданий блокировок.
Количество затребованных и предоставленных блокировок.
Оценка прпизводительносши и тестирование
Важно оценить, может ли база данных отвечать требованиям, предъявляемым приложением. Поэтому следует внимательно изучить результаты аттестации базы данных применительно к вашему приложению. Производительность приложения зависит от конкретной объектной модели и вида транзакций, выполняемых на этой объектной модели. При использовании различных объектных моделей приложение может показать разную производительность. Одни приложения лучше работают с объектной базой данных, а другие — с реляционной. И наоборот, одна объектная база данных может оказаться более подходящей для данного приложения, чем другая.
Аттестации ибщеги характера
К хорошо известным аттестациям объектных баз данных относятся Cattel и 007. Обе используют модели данных и транзакции, типичные для сред автоматизированного проектирования (computer-aided design — CAD). При этом некоторые транзакции имеют универсальный характер и вполне применимы к широкому спектру предметных областей.
Аттестация Cattel проверяет результативность реляционных и объектных баз данных. Ее разработчик Рик Кэтл (Rick G.G. Cattel) также основал и возглавил консорциум Object Database Management Group (ODMG). Данная аттестация демонстрирует, что для определенных типов приложений объектная база данных может иметь значительные преимущества в производительности перед реляционной базой данных. Она также показывает преимущества горячего кэша клиента. При использовании реляционной реализации SQL-операторы должны выполняться на сервере для каждой транзакции, поэтому такая система не может воспользоваться преимуществами горячего кэша клиента.
Аттестация 007 разработана Микаэлем Кари (Michael Carey), Дэвидом Дебитом (David DeWitt) и Джефри Нотоном (Jeffrey Naughton) в университете Висконсин- Медисон. Цель аттестации 007 — определить исчерпывающий набор тестов для оценки производительности объектных баз данных. Для реляционных баз данных пока никаких результатов не предоставлено, а из “лагеря" объектных баз данных приняло участие только несколько фирм-производителей. Полное описание аттестации 007 и полученных результатов можно получить по адресу: ftp. cs . wise. edu/007.
Аттестации специальниги назначения
Аттестации общего характера не удовлетворяют требованиям всех приложений. Приложения почти всегда имеют уникальные транзакции, которые должны отвечать определенным требованиям по производительности.
Если вас волнует, сможет ли данная реализация удовлетворить конкретным требованиям по производительности, необходимо создать некоторый прототип базы данных и подвергнуть его аттестации. Такая аттестация должна реалистически отражать величину и трудоемкость обработки данных, которые будут храниться в этой базе данных. Она должна также учесть влияние таких архитектурных компонентов, как кэши клиента и сервера, и оценить, сможет ли эта база данных соответствовать нужному уровню параллелизма. С помощью упомянутых выше аттестаций общего характера измерялась производительность работы только одного пользователя.
Когда сравнивается производительность объектной и реляционной баз данных, нужно также принимать во внимание и работу приложения, не связанную с базой данных. При этом необходимо учесть и расходы на преобразование данных из реля-
ционной базы данных в соответствующие объекты приложения. Кроме того, в разных базах данных производительность обработки объектов, уже находящихся в памяти, может быть различной, что необходимо учесть при сравнении.
На производительность влияют и другие аспекты работы приложения. Если они будут учтены, то небольшая разница в производительности между продуктами баз данных существенно не повлияет на общую производительность системы.
Аттестации объектных баз данных Cattel и 007 рассчитаны только на однопользовательскую работу. Поэтому не следует думать, что можно достичь такого же уровня производительности и при работе в многопользовательской среде. Архитектуры разных реализаций объектных баз данных отличаются способом адаптации к многопользовательской среде, и многие аспекты архитектуры базы данных, которые сказываются на производительности, не проявляют себя до тех пор, пока база данных не получит серьезную многопользовательскую нагрузку. Нередки случаи, когда после получения результатов аттестации в однопользовательском режиме дальнейшее тестирование прекращалось и принималось решение о выборе некоторого проекта. Но когда конечная система получала ощутимую многопользовательскую нагрузку, проблемы производительности тут же выходили на поверхность.
Базовые аттестации часто выполняются на стадии, когда прототип демонстрирует, что производительность будет вполне удовлетворительной. К сожалению, производительность часто повторно не оценивается непосредственно перед передачей программного обеспечения заказчику, особенно когда “все сроки уже прошли”. Однако каждое законченное приложение обеспечивает выполнение специфических для данного проекта функций, и оценка производительности “на натуре” (а не в искусственной среде) показала бы истинные результаты. Тестирование в реальных условиях помогло бы определить, стоит ли вносить определенные корректировки в следующую версию.
На данный момент нет достаточной информации об аттестациях объектных баз данных. Разработчики заинтересованы в публикации своих результатов, чтобы и другие могли ими воспользоваться. К сожалению, фирмы-производители часто затрудняют доступ к подобной информации, поскольку вряд ли отыщется такой продавец, который бы хотел опубликовать результаты, если продаваемый им продукт не демонстрирует наилучшей производительности.
Категории доступа
При оценке производительности объектной базы данных следует учесть возможность использования нескольких категорий доступа. Важно понимать полный набор требований относительно доступа к данным, которые будут размещены в базе данных.
Все приложения выполняют транзакции, которые модифицируют базу данных, т.е. вставляют, удаляют и модифицируют экземпляры. Для большинства приложений C++ огромное значение имеет эффективная навигация объектов в процессе приложения.
Приложения отличаются один от другого значимостью доступа к данным, осуществляемого с помощью запросов. Для таких областей применения объектных баз данных, как системы CAD/CAM, доступ посредством запросов не имел в прошлом большого значения. Эти приложения обычно не использовали такой специализированный язык запросов, как SQL, и реляционные базы данных не могли удовлетворять требованиям по производительности. Исторически сложилось так, что они использовали доморощенные базы данных, которые не поддерживали такой доступ. Эти приложения не могли похвастаться широтой диапазона требований к запросам, на которую могут рассчитывать нынешние пользователи. По мере расширения сфер применения объектных баз данных поддержка доступа посредством запросов становилась все более актуальной.
Факторы, влияющие на производительность
На производительность объектной базы данных оказывают влияние следующие факторы:
размер базы данных;
размер экстентов;
размер объектов;
размер коллекций;
индексы;
использование холодного или горячего кэша.
Чтобы оценить влияние этих факторов, нужно протестировать перечисленные выше переменные.
Размер базы данных
Аттестацию следует выполнять с использованием различных размеров баз данных, как меньшего, так и большего, чем ожидаемый размер. Такие тесты помогут оценить расширяемость архитектуры для баз данных большего объема. Размер базы данных может повлиять на эффективность преобразования объектной ссылки в реальный экземпляр; на затраты, связанные с поддержкой индексов при обновлении индексированных атрибутов; а также на часть базы данных, размещаемой в кэше. Для реализаций объектных ссылок, которые выполняют хеширование и подстановку, производительность может зависеть от количества экземпляров в базе данных. При небольших размерах баз данных в кэшах может содержаться довольно большая часть базы данных или даже вся база данных целиком. В этом случае доступ будет очень быстрым. По мере увеличения размера базы данных некоторые блоки, затребованные с диска, не будут помешаться в кэше базы данных. Чтобы оценить эффективность механизма управления кэшем в данной реализации, необходимо заставить кэш базы данных обрабатывать больше данных, чем он сможет содержать в памяти. Кэш клиента также может потребовать больше объектов, чем помещается в реальной памяти. В ситуациях, когда памяти требуется больше, чем имеется в действительности, производительность падает и порой довольно стремительно.
Размер экстентов
Количество экземпляров в классе может повлиять на эффективность преобразования объектного идентификатора в экземпляр. Если приложение требует большого количества экземпляров данного типа, рекомендуется, чтобы при аттестациях использовалось по крайней мере то число экземпляров, которое ожидается в проектируемом приложении.
Размер объектов
Размер объектов в приложении также может повлиять на производительность. Чем больше размер объектов, тем меньше их поместится в блоке фиксированного размера. В таких случаях целесообразно увеличить размер блока, чтобы за одну дисковую передачу можно было прочитать больше объектов. Некоторые реализации предусматривают специальные типы данных для представления очень больших объектов переменной длины, которые могут занимать много блоков. Другие же просто используют строковые объекты переменной длины, которые, возможно, не подойдут для хранения многих мегабайт видеоданных (например, MPEG-2).
Еще один фактор — количество атрибутов внутри объекта. Некоторые реализации для поддержки гетерогенности выполняют необходимые преобразования представлений объектов во время активизации и дезактивизации. При таком подходе требуется обработка на уровне атрибутов, поэтому на производительность может повлиять и количество атрибутов.
Размер коллекций
Кардинальное число коллекций и связей имеет прямое влияние на производительность. Реализация абстракций многих коллекций оптимизируется для коллекции определенного размера. Если приложение использует коллекции, кардинальные числа которых отличаются на несколько порядков, это может отрицательно сказаться на производительности. Причем такое “дурное” влияние может иметь место в следующих случаях. Если коллекции приходится управлять гораздо большим количеством элементов, чем было запроектировано, производительность от этого обязательно пострадает. А если коллекция будет содержать меньше элементов, чем предусматривалось при оптимизации, удельные расходы (в расчете на один элемент) могут оказаться выше по сравнению с удельными расходами коллекции, оптимизированной для меньшего количества элементов.
Сравнение холвдного каша с горячим
Передачи данных между клиентом, сервером и диском требуют значительных расходов ресурсов, которые оказывают существенное влияние на общую производительность базы данных. Важно сравнить ситуации с использованием холодного и горячего кэшей. При развертывании приложения существуют моменты, когда кэши будут холодными. Получение результатов только для холодного или горячего кэша может привести к неверным выводам.
Индексы
Индексы также могут влиять на производительность. В объектной базе данных индексы играют значительно меньшую роль, чем в реляционной. Индекс в объектной базе данных обычно ставится на следующие атрибуты.
Атрибут, содержащий реальный идентификатор пользователя для объекта (например, номер карточки социального обеспечения в объекте Person).
Атрибуты, которые служат в качестве вторичных ключей.
Атрибуты, которые часто используются в ассоциативном поиске.
Исторически сложилось так, что в приложениях, работающих с объектными базами данных, больше внимания уделяется эффективности обработки объектных ссылок и коллекций, поэтому фирмы-производители объектных баз данных на поддержку индексов затратили меньше усилий, чем фирмы-производители реляционных баз данных. В некоторых реализациях, например, разрешается индексировать только один атрибут.
Доступ, реализуемый приложениями
Во многих С++-приложениях, работающих с объектными базами данных, интерфейс C++, используемый для переходов между связанными объектами, является основным для доступа к базе данных. В этом разделе рассматриваются различные, предусмотренные в интерфейсе C++, варианты доступа, оценка эффективности которых включена в известные аттестации. Транзакции, выполняемые в аттестациях Cattel и 007, представляют широкий спектр вариантов доступа. Но для каждого конкретного приложения будут актуальными только некоторые из них.
Доступ н экстенту
Последовательное сканирование всех экземпляров экстента — обычная операция. Она выполняется, когда опрашиваются все экземпляры класса, чтобы найти те из них, которые отвечают условию запроса. При этом для сужения области поиска не используются ни индексы, ни другие механизмы доступа.
Доступ к экземпляру
На одном экземпляре класса может выполняться несколько операций. Следует обязательно вычислить время, необходимое базе данных для создания, обновления и удаления экземпляров. Возьмем другую операцию — преобразование имени объекта в объектную ссылку. Эта операция встречается при вызове функции lookup_obj ect для ODMG-класса d_Database.
В аттестациях чаще всего оценивается эффективность доступа к экземпляру посредством объектной ссылки, поскольку это самое популярное средство доступа к экземплярам в объектной базе данных, построенной в среде C++. Эта операция выполняется в случае, когда приложение вызывает функции operator-> или operator* класса d_Ref<T>. Следует оценить производительность для каждой из следующих ситуаций.
Экземпляр уже находится в кэше клиента.
Экземпляра нет в кэше клиента, но он находится в кэше базы данных.
Экземпляра нет ни в одном из кэшей, и поэтому его нужно считать с диска.
“Крупногабаритные” объекты переменной длноы
При проведении многих аттестаций определяется время, требуемое для доступа к большим по размеру объектам переменной длины. Время доступа включает время, необходимое для последовательного сканирования объекта целиком, а также для считывания или записи в начало, середину и конец данных. Данные объекта активизируются в кэше клиента по частям, причем размеры этих частей различны для разных реализаций, и этот фактор сказывается на производительности реализации в целом.
Доступ к атрибутам
Иногда к объекту выполняется доступ на основе значений его атрибутов. На одном или нескольких атрибутах для повышения производительности операций поиска могут быть установлены индексы, поскольку при отсутствии индекса необходимо выполнять последовательное сканирование экстента. Размещение индекса на атрибуте может как повысить производительность операций поиска, так и снизить общую производительность при добавлении элементов в индекс или изменении индексированного значения атрибута. В одних реализациях выполняется индексированный поиск на стороне клиента, а в других — на стороне сервера. Следует иметь в виду, что обработка больших индексных значений на стороне клиента существенно увеличивает количество передач между клиентом и сервером.
Дпступ к коллекциям
В любом С++-приложении широко используются коллекции, поэтому производительность их обработки имеет большое влияние на общую производительность приложения. Обычной практикой является получение доступа ко всем элементам коллекции. Один метод заключается в опросе каждого элемента с помощью такого объекта итератора, как ODMG-класс d_Iterator<T>. Другой состоит в вызове функции (предоставляемой Самой коллекцией), которая в явном виде активирует все элементы
коллекции одновременно. Если коллекция содержит элементы типа d_Ref<T>, такая операция может и активизировать адресуемые элементы. Реализации отличаются гранулярностью коллекций и их элементов, которые передаются клиенту. Выполнение операции, которая активизирует все элементы сразу, приводит к одной объемной передаче. Такой подход сокращает объем коммуникационных передач между клиентом и сервером, а посему является более эффективным. При аттестации следует оценить эффективность обоих методов доступа к элементам коллекций.
В некоторых коллекциях предусмотрены операции получения доступа к конкретному элементу. Организация поиска зависит от абстракции коллекции. Например, класс d_Varray<T> содержит операцию поиска объекта на основе значения индекса. В классе d_Set<T> предусмотрена весьма эффективная операция, позволяющая определить, содержит ли коллекция заданное значение типа т. Класс d_map<K,v> содержит операцию поиска значения типа V по данному ключу типа К. В аттестацию обязательно нужно включить оценку эффективности этих операций.
Важно также оценить эффективность операций вставки и удаления элементов коллекции. Некоторые коллекции позволяют приложению оценить начальное кардинальное число коллекции. Реализация базы данных может затем предварительно выделить необходимую память, что устранит последующую необходимость пошагового наращивания объема коллекции. Однако для некоторых коллекций это может вызвать определенные затраты. Следует непременно оценить производительность начальной операции “крупногабаритной” вставки большого количества элементов как с предварительным выделением памяти, так и без него.
Кроме того, нужно оценить расходы на удаление как отдельных элементов, так и самого объекта коллекции. Возможно, эффективнее окажется удаление целой коллекции, чем каждого элемента коллекции в отдельности.
Доступ к составным объектам
В аттестациях Cattel и 007 большое внимание уделяется быстродействию доступа к составным объектам. Инфраструктура составного объекта обеспечивает наличие инструмента для оценки следующих факторов:
разыменование объектных ссылок;
эффекты кластеризации;
доступ к большой группе взаимосвязанных объектов.
К объектам часто осуществляется групповой доступ, основанный на взаимосвязях между ними, поэтому доступ к составным объектам обеспечивает естественную единицу обработки данных для проведения аттестации.
В аттестацию необходимо включить оценку доступа как для чтения, так и записи. Общий сценарий включает следующие варианты обработки:
доступ к чтению всех подобъектов составного объекта;
доступ к чтению всех подобъектов с последующей модификацией некоторого подмножества подобъектов;
доступ к чтению и обновление всех подобъектов.
Транзакции, которые выполняют только доступ к чтению, не несут расходы на передачу модифицированных объектов обратно в базу данных по завершении транзакции. Оценив сначала расходы на чтение объектов, можно облегчить определение расходов на обновление объектов. При этом следует отдельно оценить производительность в ситуациях с холодным и горячим кэшем.
Обход составного объекта обычно выполняется в направлении “в глубину”. В данном случае нужно проводить измерения при полном, плотном и неплотном обходах. При полном обходе доступ осуществляется к каждому подобъекту внутри составного
объекта, при плотном — к большей части подобъектов, а при неплотном обходе — к незначительному их числу. Кластеризация подобъектов часто выполняется “в глубину'", чтобы кластеризовать поддеревья в иерархии составного объекта. При плотном обходе “в глубину’" будут использованы преимущества именно такой кластеризации, поскольку отношение количества подобъектов к количеству блоков будет весьма высоким. Но при неплотном обходе в ширину имеется большая вероятность считывания объектов, которые разбросаны по многим блокам. Производительность таких операций зависит от расположения объектов в блоках. Если интерфейс базы данных способен предоставить информацию о поблочном размещении объекта, то будет полезно узнать о характере использования блоков и уровне кластеризации подобъектов, к которым выполняется доступ.
Доступ посредством запросов
Для приложений, которые используют такой язык запросов, как OQL, важно оценить его производительность. Поскольку большинство фирм-производителей делают акцент на навигационном доступе средствами языка C++, очевидно, что приложения будут существенно отличаться по производительности. Если производительность обработки запросов важна для приложения, в аттестацию нужно обязательно включить используемые типы запросов. В следующих разделах рассматриваются операции, которые тестируются чаще всего. Некоторые из них связаны с операциями, выполняемыми на языке C++.
Последовательное сканирование экстента
Необходимо определить время, требуемое для последовательного сканирования экстента класса и доступа к каждому экземпляру. Механизм обработки запросов должен это делать всякий раз, когда запрос не использует таких средств получения доступа, как индексы или связи, которые служат для ограничения области действия запроса.
Поиск ни значению
Обычно выполняются запросы, использующие булевы предикаты, которые накладывают ограничения на значения атрибутов. Значения, по которым ведется поиск, помогут вам правильно установить индексы на атрибуты. Обработчик запросов должен определить, могут ли индексы использоваться для минимизации области поиска. Тестирование производительности следует провести как с использованием индексов на соответствующих атрибутах, так и без них.
Существует несколько типов поиска по значению. Запрос на точное совпадение определяет, имеют ли какие-нибудь экземпляры в классе атрибут, равный заданному значению. С помощью другого распространенного типа запроса выполняется поиск всех экземпляров, у которых значение заданного атрибута лежит внутри диапазона значений. Популярен также дизъюнктивный запрос, включающий оператор or, который можно использовать для тестирования эффективности обработчика запросов, выбирая между несколькими индексами в классе.
Соединение
Соединение используется для установления динамических связей между экземплярами на основе определенных ограничений (накладываемых на значения), которые должны существовать между этими атрибутами. В объектной базе данных соединения применяются не очень часто. Если в некотором приложении этот вид запроса предполагается использовать довольно часто, то следует сравнить результаты работы при ис-
пользовании как объектной, так и реляционной базы данных. Обработка соединений —- основная цель оптимизации в реляционных базах данных, которым, возможно, придется отдать предпочтение, если эта операция является критичной для данного приложения. Однако следует иметь в виду, что многие соединения, выполняемые в реляционной базе данных, лучше обрабатываются объектными ссылками и коллекциями в объектной базе данных. Главное определить, какими будут связи — динамическими или статическими. Реляционные базы данных могут показать более высокий результат, если соединения выполняются на основе связей, которые должны быть установлены динамически.
Обход связей
Производительность навигационного доступа следует оценить при обходе связей между объектами. При этом нужно определить количество уровней разыменования в конечном приложении.
Возможны транзакции, предназначенные для обхода связей, а также определения того, будет ли объект в конечной точке перехода иметь атрибуты с заданными значениями. Такие запросы называются путевыми (path query). Если исследуемая реализация поддерживает путевые индексы, в аттестацию следует обязательно включать эту операцию, причем как с использованием путевого индекса, так и без него.
Объектная мидель приложения и варианты gncmgna
Для проекта, предусматривающего высокопроизводительную работу базы данных и приложения, очень важно установить соответствующую объектную модель, которая поддерживает варианты доступа к базе данных, используемые приложением. Объектная база данных идеально подходит для объектных моделей, которые имеют большое количество взаимосвязей между объектами, используемыми в качестве основного средства доступа. В таких ситуациях можно добиться довольно высокой производительности. В объектной базе данных предусмотрены эффективные физические механизмы для обхода статических связей между объектами.
Разработчики приложений должны определить объектную модель, которая как можно точнее отображает реальные абстракции. При проектировании объектной базы данных следует рассмотреть различные варианты доступа к базе данных, используемые приложением.
При работе с объектной базой данных в среде C++ ее схема будет состоять из множества С++-классов, которые используются всеми приложениями в качестве представления объектной модели в памяти. Поэтому важно учесть требования к работе каждого приложения, которое будет получать доступ к базе данных. Такой подход поможет выработать проектные решения для схемы базы данных. Например, абстракции коллекций отличаются способностью эффективно выполнять операции доступа, вставки и удаления элементов. В одних приложениях необходимо выполнять только однонаправленную итерацию коллекции, в то время как другие не могут обойтись без эффективного поиска в коллекции по заданному значению или двунаправленной итерации. Если схема базы данных не обеспечивает представления, которое нужно приложению, то разработчикам приложения, по всей вероятности, придется создавать собственные структуры доступа. При этом увеличиваются расходы, связанные с созданием и поддержкой приложения, а также затраты системных ресурсов во время его выполнения, и это сводит на нет цель использования объектной базы данных.
Важно определить конструкции моделирования, необходимые для представления объектной модели приложения. Некоторые конструкции моделирования, например коллекции, шаблоны и множественное наследование, и определенные физические
механизмы доступа (ключи и индексы) по-разному поддерживаются различными фирмами-производителями.
В схеме объектной базы данных заключена информация, которая описывает концептуальную и логическую модель, используемую приложениями. Эта логическая информация обычно преобразуется в физические представления, которые обеспечивают эффективный доступ к данным. Прямая поддержка логической модели на физическом уровне дает хороший шанс на достижение высокой производительности.
И наоборот, при использовании реляционной базы данных практически не имеет значения, какая именно применяется объектная модель приложения. Для представления ссылок на объекты обычно используются внешние ключи, а для ускоренного доступа к элементам коллекции — индексы. Но данные должны быть динамически связаны на основе значений в столбцах. Реляционная модель данных предоставляет большую гибкость в установлении произвольных связей между данными, но эта гибкость достается дорогой ценой. Однако гибкость нужна не всегда, и многие связи, которые должны устанавливаться динамически, на самом деле являются статическими, которые известны уже на этапе проектирования.
Расходы в реляционных базах данных включают “розыскные работы” в индексах, сравнения значений атрибутов и доступ к строкам, которые разбросаны по разным дисковым блокам. Реляционные строки затем должны быть преобразованы в С++- представление. Этот процесс может включать заполнение коллекций, что требует расходов на операции вставки, которые для некоторых коллекций могут быть весьма недешевыми. Для упорядоченных коллекций необходимо также выполнить операцию сортировки в каждом SQL-запросе или самом приложении.
Объектные базы данных оптимизируются для эффективного представления коллекций. Поскольку реализация объектной базы данных обеспечивает представление, используемое как на диске, так и в памяти, благодаря оптимизации может быть выбран наиболее удачный вариант эффективного представления и передачи данных. Коллекции могут передаваться в “предбанник” приложения; коллекции, которые упорядочиваются или требуют заполнения структур доступа, могут избежать больших затрат, если они активизируются в кэше приложения. Повышению производительности также способствует кластеризация связанных объектов.
Глава 16
Схемы баз данных
Система управления базой данных поддерживает описание всех объектов (типов данных), хранимых в базе данных. Такое описание, именуемое схемой, обычно находится в области, называемой словарем данных. Сама реализация базы данных и такие программные средства, как генераторы запросов, отчетов и форм, используют схему для манипуляции данными, хранимыми в базе данных.
Некоторые объектно-ориентированные языки программирования и среды содержат множество объектов, именуемых метаобъектами, или метаклассами. Эти метаклассы обеспечивают описания классов, объявленных в приложении. К сожалению, в языке C++ не предусмотрено стандартного представления для информации метаклассов.
Словарь базы данных и метаклассы объектно-ориентированной среды программирования предоставляют практически одну и ту же информацию, поскольку схема объектной базы данных выводится из объявлений классов приложения. В схеме должно быть определено полное описание каждого типа, экземпляры которого хранятся в базе данных. При этом поддерживается большая часть информации, содержащаяся в С++-объявлениях типов.
Технологии построения баз данных, не использующие объекты, не испытывают большой необходимости в информации об объектной модели приложения. По сравнению с такими технологиями, как реляционные базы данных, сведения, содержащиеся в схеме объектной базы данных, обычно более обширна, благодаря информации об объектной модели приложения, которая может быть объявлена в объектноориентированном языке программирования.
Программы приложения могут извлечь много пользы из доступа к информации схемы базы данных. Сторонние провайдеры вспомогательных средств могут разработать средства, которые не зависят от схемы базы данных. Разработчики могут написать броузеры баз данных для взаимодействия с объектами базы данных, не вникая в детали представления классов, заложенных в программы приложения.
В версии 2.0 стандарта ODMG определен набор классов, которые предоставляют доступ к информации схемы в объектной базе данных. В разных реализациях объем этой информации различен. В этой главе приводится общее описание метаклассов ODMG, а приложение В содержит полное определение каждого из них. Реализации этих классов не были доступны на момент написания данной книги, поэтому никаких
практических примеров здесь не приводится, хотя упоминаются другие характеристики схемы, обеспечиваемые реализациями. Для получения точной информации обратитесь к документации конкретной реализации. Здесь также рассматриваются различные формы модификации схемы. Глава завершается обсуждением миграции баз данных и их объектов из одной версии схемы в другую.
Захват схемы
То, что определение схемы базы данных должно опираться на С++-объявления классов, вносит много сложностей в жизнь фирм-производителей объектных баз данных. Реализация должна собрать информацию о типах (о ней и пойдет речь в этой главе) путем анализа заголовочных файлов C++. Каждая фирма-производитель должна провести С++-экспертизу и разработать средства для анализа типов приложения. Поэтому основной груз ложится на плечи разработчиков базы данных, которые, помимо средств проведения экспертизы базы данных, должны иметь средства проведения экспертизы на языке C++.
Многие фирмы-производители получили лицензию на использование технологии транслятора C++ cfront от фирмы AT&T и модифицировали ее для получения информации, необходимой для схемы базы данных. Но транслятор cfront уже не соответствует сегодняшнему уровню усовершенствованного языка C++. Поэтому многие фирмы используют данные символьных таблиц, предоставляемые другими доступными компиляторами. Однако каждый компилятор имеет собственное представление объявлений типов C++, и поэтому реализации баз данных зависят от используемого компилятора. Компиляторы имеют различия в следующих областях.
. Расположение членов класса внутри объекта. .
. Метод, используемый для поддержки полиморфизма.
. Метод, используемый для поддержки множественного наследования.
Каждый из этих аспектов оказывает влияние на размещение и представление объектов в памяти.
По мере эволюции и усложнения языка C++ фирмам-производителям также пришлось усовершенствовать их средства захвата схемы и представления данных. Несогласованность между компиляторами C++ является серьезной задачей для фирм- производителей, поскольку они вынуждены модифицировать свои средства для каждой реализации компилятора. Возможно, источником самых больших проблем были шаблоны C++. В прошлом в определении С++-шаблонов существовало немало противоречий, в результате чего производители компиляторов реализовали собственные интерпретаторы. Эти противоречия в основном были разрешены благодаря стараниям комитета стандартов C++, но “разнокалиберные” реализации компиляторов (а, значит, и их пользователи) еще не идут в ногу со стандартом.
Фирмы-производители объектных баз данных и других средств были бы в большом выигрыше от стандартного представления метаклассов для языка C++. Отсутствие такого стандарта и создает основные трудности для разработчиков. К сожалению, комитет 1SO/ANS1 C++ его не определил. Средство получения динамической информации о типах в C++ работает недостаточно хорошо. Если информация о метаклассах была бы стандартизирована и поддерживалась всеми производителями компиляторов, то создатели объектных баз данных затрачивали бы меньше усилий на анализ языка C++ и больше уделяли внимания своей основной технологии. Это не только бы упростило реализации объектных баз данных, но и привело бы к более высокому уровню переносимости для всех программных средств, которые должны понимать определения С++-классов.
Чтобы словарь данных можно было заполнить описанием классов приложения, реализации баз данных предоставляют утилиту захвата схемы. При этом фирмы- производители используют несколько методов.
Язык определения схемы генерирует как схему, так и файлы заголовков C++.
Препроцессор просматривает исходные заголовочные файлы C++, собирая данные для схемы и информацию, на основании которой создаются новые заголовочные файлы для приложения.
Постпроцессор просматривает содержимое символьных таблиц, сгенерированных компилятором, что избавляет от необходимости анализировать исходный код C++.
Каждая фирма-производитель в своей реализации стандарта ODMG и собственных продуктах использует по крайней мере один или даже несколько подходов.
При использовании любого метода захвата схемы образуется (программным путем или вручную) множество заголовочных файлов и файлов реализации, которые служат в качестве входных данных для компилятора C++. Средство захвата часто генерирует дополнительный исходный код для каждого класса, который должен быть скомпилирован и скомпонован с приложением. Этот код обеспечивает выполнение действий, необходимых для поддержки динамического управления объектами в кэш-памяти приложения. Выполняемое приложение включает библиотеки базы данных вместе с этими дополнительными объектными файлами, специфическими для схемы конкретного приложения.
Первый метод захвата схемы — самый простой в реализации. Для задания схемы применяется язык определения объектов (Object Definition Language — ODL) или графический интерфейс пользователя. Операторы языка ODL используются в качестве входных данных инструмента, который генерирует как заголовочные файлы C++, так и соответствующую схему базы данных. Преобразование операторов ODL в структуры языка C++ не регламентировано стандартом ODMG, поэтому, по всей вероятности, С++-объявления классов, сгенерированные разными реализациями, будут различными. Программы, входящие в состав приложения, полностью зависят от этих объявлений классов и, следовательно, не будут переносимыми между реализациями.
Средство, генерирующее С++-объявления классов, имеет полный контроль над стилем кодирования, но многие С++-программисты предпочитают сами управлять стилем объявлений классов. Кроме того, многие конструкции C++, необходимые разработчикам, не поддерживаются в языке ODL. В настоящее время большинство фирм-производителей не планирует использовать ODL в качестве основы для определения схемы базы данных.
Препроцессор просматривает исходные заголовочные файлы C++, определенные программистом приложения. Он собирает информацию о схеме прямо из С++- объявлений классов. Такой подход используют реализации, которые рассчитывают на стандартный синтаксис C++, а также те, которые работают с некоторыми языковыми расширениями. При нестандартных расширениях C++ без использования препроцессора не обойтись: языковые расширения транслируются в эквивалентный стандартный синтаксис C++, а затем служат в качестве входных данных для компилятора C++.
Некоторые реализации выполняют захват схемы с помощью утилиты постпроцессора, которая просматривает содержимое символьных таблиц, входящих в состав объектных файлов, созданных компилятором. Из этих данных и выводится информация об объявлениях классов. Представления символьных таблиц различны для разных компиляторов, поэтому постпроцессор специфичен для компилятора и зависит от его уровня детализации информации о классе.
Доступ к схеме
Версия 2.0 стандарта ODMG предоставляет набор метаклассов, который позволяет приложению получить доступ к схеме базы данных. Содержимое схемы представляет собой множество взаимосвязанных метаобъектов, которые описывают типы, хранимые в базе данных. Конкретные реализации могут иметь физическое представление схемы, которое отличается от описываемых здесь метаклассов ODMG. Эти метаклассы необязательно должны быть потенциально перманентными объектами, которые хранятся непосредственно в базе данных.
Вместо обеспечения прямого доступа к коллекциям связанных метаобъектов, предусмотрены соответствующие функции доступа. Если метакласс А имеет связь типа “к-одному” с другим метаклассом В, то вы можете использовать одну функцию, которая возвращает ссылку на связанный метаобъект В. Если между метаклассами А и В существует связь “ко-многим”, то метакласс А имеет две функции, возвращающие итераторы, которые ссылаются на экземпляры метакласса в. Одна функция возвращает итератор, расположенный в позиции первого элемента; ее имя содержит суффикс begin. В имени второй функции есть суффикс end, и она возвращает итератор, расположенный за последним элементом. На рис. 16.1 — 16.12 эти связи имеют имена, стоящие перед суффиксами _begin и _end. Функции begin и end, а также итераторы, которые они возвращают, соответствуют соглашениям для интерфейсов стандартной библиотеки шаблонов (Standard Template Library — STL). Приложение В содержит описание операций этих итераторов и функций begin и end для каждого класса.
Области действия имен
В основе схемы базы данных лежит система типов приложения. Каждый компонент схемы имеет имя, которое относится к конкретной области действия имен. Для описания разных типов, которые могут быть определены в приложении, используются различные метаклассы. Каждый такой метакласс выводится из базового класса d_Meta_obj ect. Каждый экземпляр класса d_Meta_Obj ect имеет уникальное имя, относящееся к соответствующей области действия. Имя экземпляра возвращается функцией name, предусмотренной в этом классе. К области действия экземпляра класса d_Meta_Object можно получить доступ, вызвав функцию defined in. Сама область действия представляется метаклассом d_Scope. Между метаклассами d_Scope и d_Meta_Object существует связь “один-ко-многим” (рис. 16.1). Класс d_Scope содержит функцию resolve, которая возвращает ссылку на экземпляр класса d_Meta_Object с конкретным именем. В классе d_Scope предусмотрены функции, возвращающие итераторы (в стиле библиотеки STL) для доступа к экземплярам класса d_Meta_object, определенным в конкретной области действия.
Рис. 16.1. Области действия и метаобъекты
Некоторые метаклассы, выведенные из класса d_Meta_Object, вводят новую область действия. Эти классы также являются производными класса d_Scope, чтобы обеспечить пространство имен для компонентов их метаобъекта. Ниже перечислены эти метаклассы.
d_Structure
d_Class
d_Enumeration_Type
d_Operation
d_Module
Поскольку некоторые экземпляры класса d_Meta_obj ect могут быть также экземплярами класса d_Scope, в схеме может быть создана иерархия именованных областей действия, что позволяет включать области действия в объектно-ориентированный язык программирования.
Модули
Модуль охватывает некоторое множество типов в словаре данных и используется для представления конкретной версии схемы. Класс d_Module представляет некоторый модуль в словаре. Для реализаций, которые поддерживают несколько версий схемы, каждая такая версия должна быть представлена отдельным экземпляром класса d_Moduie. Экземпляр класса d_Module является метаобъектом и представляет при этом некоторую область действия, поэтому он выводится из обоих классов: d_Scope и d_Meta_Object, как показано на рис. 16.2. Можно построить иерархию экземпляров класса d_Module. Класс d_Module содержит статическую функцию top_level, которая возвращает ссылку на экземпляр класса d_Module, находящийся на вершине иерархии метаобъектов базы данных. Эта функция используется как точка входа в сплетение взаимосвязанных метаобъектов словаря.
Класс d_Module содержит коллекции других метаобъектов. Класс d_Constant представляет константные значения, которые должны храниться в словаре данных. Каждый метакласс, используемый для представления конкретной категории типа, выводится из класса d Туре. Операции представляются экземплярами класса d Operation. Операция, которая является глобальной и не включается в область действия конкретного класса, связана непосредственно с экземпляром класса d_Module.
Рис. 16.2. “Родословная” модуля
Операции
Операция — это либо глобальная функция, либо функция-член некоторого класса. Класс d_Operation представляет любую операцию и выводится из обоих классов d_Scope и d_Meta_Object (рис. 16.3). Операция имеет имя, к которому можно получить доступ, вызвав функцию name, унаследованную от класса d_Meta_Object. Любая операция имеет список параметров, тип возврата и набор исключений, которые могут быть сгенерированы. Функция is static возвращает истинное значение, если данная операция является статической функцией-членом класса. Если операция связана с классом, то она также имеет спецификатор доступа, означающий, является ли она открытой (public), закрытой (private) или защищенной (protected). Экземпляр класса d_Parameter имеет имя, которое определяется в области действия соответствующего экземпляра класса d_Operation. Функция parameter_type возвращает ссылку на экземпляр класса d_Type, который представляет тип параметра.
Класс d_Exception представляет исключение. Одно исключение может быть сгенерировано многими операциями, а одна операция может сгенерировать много исключений. Следовательно, между метаклассами d_Operation и d_Exception существует связь “многие-ко-многим”. Экземпляр класса d_Exception имеет имя внутри области действия своего экземпляра класса d_Module. Исключения в языке программирования представляются типами, а класс d_Exception содержит функцию exception_type, которая возвращает ссылку на экземпляр класса d_Type, представляющий тип данного исключения.
Рис. 16.3. “Досье" на операцию
Домены
Метакласс d_Primitive_Type представляет примитивные типы языка C++. Он выведен из класса d_Type, как показано на рис. 16.4. Внутри этого класса определено перечисление d Typeld, которое имеет идентификатор для каждого типа. Конкретное значение этого перечисления возвращается функцией-членом type_id.
Ссылки
Указатели и экземпляры класса d_Ref<T> представлены метаклассом d_Ref_Type. Внутри класса определено перечисление d_Ref_Kind, которое возвращается функцией, уточняющей, является ли данная ссылка указателем или она имеет тип d_Ref<T>. Предусмотрена также функция, возвращающая ссылку на экземпляр класса d_Type, который представляет тип адресуемых данных, как показано на рис. 16.4.
Рис. 16.4. Примитивы, ссылки и коллекции
Коллекции
Метакласс d_Collection_Type используется для представления типа коллекции. В этом классе определено перечисление d_Kind, которое используется для обозначения вида коллекции. Предусмотрена здесь и функция для возврата типа элементов, содержащихся в коллекции.
Метакласс d_Keyed_Collection_Type выведен из класса d_Collection_Type для представления коллекций, содержащих пары ключ/значение, например d__Dictionary<K, V>, d_map и d_multimap. Этот метакласс и его связи с другими метаклассами показаны на рис. 16.4. В нем предусмотрены функции для возврата типов составляющих пары коллекции: ключа и соответствующего ему значения. Типом элемента, возвращаемого базовым классом d_Collection_Type, является экземпляр класса d_Association (класс типов ключа и значения).
Свойства
Структуры и классы имеют свойства. Свойство имеет имя и тип. Метакласс d_Property, представляющий эти свойства, выведен из класса d_Meta_object (рис. 16.5). Функция type_of возвращает ссылку на экземпляр класса d_Type, представляющий тип данного свойства. Поскольку любой тип могут использовать несколько свойств, между классами d_Type и d_Property существует связь “один-ко- многим”. Функция name, унаследованная от класса d_Meta_Object, возвращает имя свойства. Доступ к значению такого атрибута, как спецификатор доступа (public, private или protected), возвращается функцией access_kind.
Рис. 16.5. Свойства
Атрибуты
Класс d Attribute используется для представления членов структуры или класса. Он наследует имя и характеристики типов от своего базового класса d_Property. Если атрибут в действительности представляет собой встроенный массив значений, то функция dimension возвращает ненулевое значение, соответствующее размеру массива. Функция is_read_only возвращает истинное значение, если данный атрибут объявлен с использованием модификатора const. Функция is static возвращает истинное значение, если данный атрибут является статическим данным-членом класса.
Собственные интерфейсы реализаций часто предоставляют следующую дополнительную информацию.
Относительное смещение атрибута внутри экземпляра, пока он находится в памяти (зависит как от компьютера, так и компилятора).
Относительное смещение в базе данных.
Является он транзитным или перманентным.
Эти характеристики атрибутов нужны реализации, но не имеют большого значения для приложения. Некоторые из них запрашиваются реализациями, когда экземпляр активизируется в кэше приложения.
Структуры
Структура представляет собой тип, который имеет набор атрибутов. Класс d_Structure_Type выведен из двух классов: d_Type и d_Scope, а его экземпляр содержит коллекцию экземпляров класса d_Attribute, которые определяют атрибуты этого типа (рис. 16.6).
Рис. 16.6. Класс d_Structure_Type
Классы
Класс — это тип, который устанавливает область действия для своих членов. Он представляется экземпляром метакласса d Class, который выведен из двух классов: d_Type и d_Scope. На рис. 16.7 показаны метаклассы, экземпляры которых определены внутри области действия экземпляра класса d Class. Подобно другим метаклассам, имя класса можно узнать, вызвав функцию паше, унаследованную от класса d_Meta_Object. '
Рис. 16.7. Класс d_ClassРис. 16.7. Класс d_Class
Рис. 16.7. Класс d_Class
Рис. 16.7. Класс d_Class
Конкретные реализации могут предоставить и другую информацию.
. Пространство имен, в котором содержится класс (каждое С++-пространство имен может быть представлено различными экземплярами класса d_Module).
. Для параметризованного класса представлен список его шаблонных параметров.
. Булев признак, означающий, имеет ли класс какие-либо виртуальные функции.
. Булев признак, означающий, является ли класс потенциально перманентным.
Связи .
Двунаправленная связь между классами А и В представляется экземплярами классов d_Rel_Ref, d_Rel_List или d_Rel_Set в экземплярах А и В, которые ссылаются один на другого, как описано в главе 8. Каждое объявление в классе члена, “отвечающего” за связь, представляется в схеме экземпляром класса d_Relationship, связанным с соответствующим экземпляром класса d_Class. Для доступа к противоположному члену связи используется функция-член inverse. Перечисление указывает тип члена, определяющего связь.
Наследование
Классы могут участвовать в отношениях наследования, которые представляются экземплярами метакласса d_lnheritance. Метакласс d_Class содержит коллекции объектов d lnheritance, которые представляют их базовый и производные классы (подклассы). Экземпляр класса d_Inheritance имеет связи derives_from и inherits_to с двумя экземплярами класса d_Class (рис. 16.8). В классе d lnheritance предусмотрены функции, позволяющие узнать о характере доступа к базовому классу (d_Access_Kind) и его виртуальности.
1sub_class list
«метакласс» d_Class«метакласс» d_Classderives_from* «метакласс»
«метакласс» d_Class
«метакласс» d_Class
Jbase_class_listx d_Inheritance
inherits_to (Упорядоченная» коллекция)
Рис. 16.8. Наследование
Рассмотрим иерархию классов с одиночным и множественным наследованием, показанную на рис. 16.9. Она представляется на языке C++ следующими объявлениями классов.
classА{. . . };
classВ:publicvirtualА{...};
classС:publicvirtualА{...);
classD:publicВ, publicC{... };
Рис. 16.9. Пример наследования классов
На рис. 16.10 показаны экземпляры классов d_Class и d_Inheritance, которые должны представлять отношения наследования среди этих классов. Стрелки указывают противоположные члены отношений между классами d_Class и d_Inheritance. Функции “наследственных” связей возвращают ссылки на соответствующие экземпляры классов d Class и d_Inheritance, а не на противоположные члены.
Рис. 16.10. Экземпляры классов d_Class и djnheritance в модели иерархии классов
Перечисления
Метакласс d_Enumeration_Type описывает тип, экземпляры которого состоят из множества идентификаторов. Он представляет область действия, в которой экземпляры класса d_Constant являются именованными (рис. 16.11). Этот класс выведен из классов d_Primitive_Type и d_Scope.
Каждый экземпляр класса d Constant, связанный с конкретным экземпляром класса d_Enumeration_Type, должен иметь тот же тип. Идентификаторы перечисления можно получить, обратившись к функции name. Например, класс d_Date содержит перечисление Month. Оно имеет идентификаторы для каждого месяца года: January, February, March и т.д. Экземпляр класса d_Enumeration_Type с именем "Month" должен быть определен в области действия экземпляра класса d Cluso с именем "d_Date". С этим экземпляром класса d_Enumeration_Type должно быть связано 12 экземпляров класса d_Constant с именами "January", "February", "March" и т.д. Они будут ссылаться на некоторый экземпляр класса d_Primitive_Type, имея при этом значения 1, 2, ..., 12.
«метакласс»«метакласс»«метакласс»
d_Scoped_Primitive_Type d_Meta_Object
гs^
used_in_constant
Рис. 16.11. Класс d_Enumeration_Type
Псевдонимы шипов
Псевдоним типа представляет новое имя для существующего типа. Возможность определения псевдонима типа часто позволяет упростить длинное или сложное имя. Для представления псевдонима типа служит метакласс d_Alias_Type. Имя псевдонима типа можно узнать, вызвав функцию name, которая унаследована от класса d_Meta_Object. Этот класс содержит функцию alias_type, возвращающую ссылку на экземпляр класса d_Type, для которого он предоставляет альтернативное имя (рис. 16.12).
Рис. 16.12. Класс d_Alias_Type
Модификации схем
Класс постоянно находится в развитии. Некоторые реализации поддерживают несколько версий схемы. Если при этом разрешается миграция экземпляров из одной версии схемы в другую, то такие реализации должны поддерживать полную историю их развития, чтобы экземпляры можно было усовершенствовать. Реализации часто разрешают программам приложения делать обход всех версий схемы, хранимых в базе данных. В следующих разделах рассматриваются варианты модификаций, которым разработчики приложения могут подвергать схемы баз данных.
Изменения классов схемы нужно вносить в исходные данные, используемые утилитой захвата схемы, а не в данные результата ее работы. Для этого есть две причины. Вам придется еще не раз запускать средство захвата схемы, чтобы получить новое определение класса, а изменения, внесенные в файлы, уже сгенерированные утилитой захвата, не так-то просто включить в новые версии этого файла.
Средство захвата схемы может определить, какие изменения были внесены в класс, и зарегистрировать любые новые типы в словаре данных. При этом оцениваются различия в классе, чтобы определить, можно ли экземпляры этого класса переносить из старой формы в новое определение класса.
Модификации на дровне класса
Под модификациями на уровне класса подразумеваются следующие изменения:
создание нового класса;
изменение статуса класса — с конкретного на абстрактный;
переименование класса;
удаление класса.
Создание нового класса не требует больших расходов при усовершенствовании базы данных, поскольку в этом случае еще нет экземпляров, которые нужно было бы модифицировать. Выполняя преобразование конкретного базового класса в абстрактный, необходимо преобразовать все существующие экземпляры в экземпляры заново выведенного конкретного класса. Переименование класса не требует больших затрат во время миграции схемы, но затронет С++-приложения, использующие старое имя класса.
При удалении класса возникает несколько проблем. В этом случае нужно удалить экземпляры из базы данных и изменить другие классы, содержащие встроенные экземпляры удаляемого класса. Если на какие-либо экземпляры удаляемого класса ссылаются другие объекты, в такой ситуации есть несколько вариантов поведения, зависящих от реализации объектных идентификаторов и ссылок в базе данных. Один вариант — найти такие ссылки и обнулить их. Другой — позволить ссылкам указывать на удаленные экземпляры. Когда при использовании такой ссылки обнаружится, что адресуемый объект удален, будет сгенерировано исключение d_Error_RefInvalid. Такой вариант подойдет только в том случае, если идентификаторы объектов не используются повторно. Еще один вариант состоит в запрещении удалять экземпляр до тех пор, пока из базы данных не будут удалены все ссылки на него.
Модификации атрибутов
Некоторые модификации схем могут быть связаны со следующим изменением атрибутов:
добавление атрибута (с предоставлением значения, действующего по умолчанию);
удаление атрибута;
изменение в объявлении порядка следования атрибутов.
Изменение порядка, в котором объявляются данные-члены, вызывает изменение их смещения в памяти. В отдельный атрибут класса можно внести следующие модификации:
изменение имени;
. изменение типа;
изменение доступа (public, private, protected);
. изменение модификатора const;
. изменение транзитности.
Статические члеиы
Статические члены зависят от класса и не влияют на отдельные экземпляры класса. Поддерживаются следующие типы модификаций:
добавление или удаление статических членов, включая функции и данные;
изменение типа статических данных-членов;
. изменение типа возврата или сигнатуры (типов параметров) статической функции.
Изменение наследования
Любой класс может участвовать в отношениях наследования с другими классами. Внесение изменений в отношения наследования может изменить представление или место в иерархии многих классов. Ниже перечислены следующие изменения:
наследование от нового или существующего класса;
удаление базового класса из отношения наследования;
изменение порядка, в котором объявляются базовые классы;
изменения, связанные с виртуальностью базового класса;
изменение классификации экземпляра.
При изменении порядка объявления базовых классов изменяется физическое местоположение экземпляров классов. Изменение классификации экземпляра означает, что он должен стать экземпляром другого класса, часто базового или производного от существующего класса. Когда изменение классификации экземпляра происходит в сторону производного класса, его члены должны быть инициализированы значениями, действующими по умолчанию.
Коллекции
При модификации схемы базы данных в коллекции могут быть внесены следующие изменения:
изменение типа элемента;
изменение абстракции коллекции (например, тип d_List заменяется типом d_Varray);
преобразование в С++-массив или из него.
Операции
При модификации схемы базы данных могут быть выполнены изменения, касающиеся следующих операций:
добавление, удаление или переименование операций;
добавление первой виртуальной функции или удаление единственной виртуальной функции из класса;
изменение типа возврата;
добавление или удаление аргумента;
изменение типа аргумента;
модификация реализации операции;
изменение доступа операции.
Если класс содержит хотя бы одну виртуальную функцию, его экземпляр имеет встроенный vtbl-указатель. Его существование влияет на размещение объекта и процедуры, используемой при активизации экземпляра.
Многоступенчатые изменения
Обычно несколько изменений схемы выполняются в виде целой группы. К ним нужно относиться как к атомарной транзакции: следует довести до конца либо все изменения, либо ни одного, поскольку между подобными изменениями часто существуют взаимозависимости. Миграция в новую версию схемы может потребовать, чтобы изменения были выполнены в определенной последовательности, или же одно изменение может иметь условие, чтобы сначала были выполнены другие изменения.
Выполненное в отрыве от других, изменение схемы может нарушить определенные условия согласованности схемы. В результате может потребоваться применение контекста других изменений схемы. Часто оказывается невозможной проверка допустимости отдельного изменения схемы, поэтому проверяется целая группа изменений.
16.4. Миграция экземпляров
При миграции экземпляров из одной версии схемы в другую требуется инициализация новых компонентов и преобразование значений из одного типа в другой. Здесь важно с максимальной результативностью привести старое состояние объектов к новому.
Новые данные-члены необходимо инициализировать. Многие реализации просто инициализируют новые примитивные данные-члены двоичными нулевыми значениями. Что касается классов, определенных пользователем, некоторые реализации позволяют для новых членов, которые встроены в объект, выполнять стандартный (нулевой) конструктор.
Преобразования
Член класса может быть подвергнут преобразованию типа. В этом разделе рассматриваются различные преобразования, обычно выполняемые с экземплярами, мигрировавшими из одной версии схемы в другую.
Преобразования на уровне класса
При модификации класса необходимо изменить как его прямые экземпляры, так и все экземпляры каждого производного класса. Изменение отношения наследования обычно требует модификации всех экземпляров в иерархии классов.
Преобразования атрибутов
К атрибутам можно применять преобразование типов. Например, при инициализации числовые типы часто могут “выручать” друг друга. Так, тип short можно преобразовать в тип long без потери данных. Однако при изменении целого типа в сторону меньшего размера представления (из-за потери точности) существует потенциальная опасность переполнения. Реализации отличаются обработкой подобных преобразований — без проверки на переполнение или с возбуждением соответствующего исключения.
Конструктор или оператор присваивания могут быть определены для установки экземпляра нового типа со значением старого типа. Если такой оператор доступен, его и следует использовать при миграции схемы. Однако многие реализации не поддерживают такой возможности.
Если некоторый атрибут состоит из вложенных объектов или структур, то к каждому вложенному компоненту нужно рекурсивно применить все необходимые “миграционные” действия.
Проблематичные преобразования
Некоторые преобразования выполнить невозможно, например преобразование структуры в примитивный литерал, структуры — в коллекцию, коллекции — в структуру. В таких случаях вообще никакие преобразования не имеют смысла. Некоторые фирмы-производители позволяют приложениям для выполнения преобразований из одного типа в другой определять функции трансформации.
Преобразования ссылок
Ссылку на экземпляр типа Тъ т.е. экземпляр типа d_Ref<Ti>, можно изменить так, чтобы она указывала на экземпляр другого типа т2. Если класс т2 является базовым для класса Тъ то не исключено, что адресуется тот же самый экземпляр, поскольку он также является экземпляром базового класса. В противном случае ссылка должна быть обнулена.
Реализация объектных идентификаторов и ссылок существенно влияет на миграцию ссылок во время изменений схемы. Если объектный идентификатор содержит информацию о физическом местоположении объекта и адресуемый экземпляр изменен так, что его нужно переместить на другое место, то идентификатор этого объекта будет изменен. Следовательно, все ссылки на этот объект придется изменить, чтобы они отражали новое значение идентификатора этого объекта.
Преобразования коллекций
Преобразованиям может подвергаться либо сам объект коллекции, либо тип его элементов. При изменении типа элементов делается попытка привести элементы к их новому типу. Некоторые коллекции можно преобразовать в другую абстракцию коллекции. Если такое преобразование невыполнимо, новая коллекция окажется пустой.
Функции трансформации
Некоторые реализации позволяют дизайнеру схемы базы данных связывать функцию трансформации с каждым классом, который должен быть изменен. Эта функция использует значения атрибутов из старого экземпляра для инициализации нового экземпляра. Такой метод дает дизайнеру полный контроль над преобразованием.
Иногда необходимо изменить тип атрибута, но при этом никакого стандартного преобразования автоматически выполнить нельзя. В таком случае для преобразования можно использовать следующую последовательность действий.
Добавьте новый атрибут с нужным типом, но с другим именем.
Напишите процедуру для инициализации нового атрибута, используя старый атрибут.
Удалите исходный атрибут. .
Переименуйте новый атрибут с исходным именем.
Миграция баз данных
При изменении схемы базы данных необходимо изменить
саму схему;
экземпляры;
приложения.
Схема изменяется при импортировании в базу данных нового определения классов. Преобразование экземпляров можно выполнить немедленно или отложить до тех пор, пока к ним не будет обращаться приложение.
C++-приложения компилируются с конкретным определением каждого класса. Поскольку база данных и приложение совместно используют одну и ту же систему типов, приложения должны иметь то же представление, которое получают экземпляры из базы данных. Если какие-то приложения не будут преобразованы одновременно с базой данных, то во время работы они, все еще опираясь на старое определение схемы, могут встретить экземпляр, основанный уже на более новой версии схемы. В таком случае возбуждается исключение d_Error_DatabaseClassMismatch.
Немедленное преобразование
При немедленном преобразовании ко всем экземплярам получает доступ программа обслуживания базы данных, и они обновляются для новой версии схемы. Преимущество немедленного преобразования в том, что после этого на производительность работы приложений не влияют расходы ресурсов, затрачиваемые на миграцию экземпляров в динамике. Но производительность может серьезно пострадать, если преобразование выполняется при работающей базе данных. Некоторые реализации поддерживают немедленное преобразование только в том случае, если на время этого процесса база данных переходит в автономный режим работы.
Отложенное преобразование
Некоторые реализации позволяют отложить преобразование экземпляров до тех пор, пока к ним не обратится приложение. Прежде чем к экземпляру будет получен доступ, база данных может претерпеть несколько изменений схемы, поэтому база данных поддерживает регистрацию всех определений класса и внесенных в них изменений. При таком подходе экземпляр любой версии класса может быть приведен к самому последнему определению. Программа обслуживания базы данных последовательно применяет изменения схемы из текущей версии экземпляра, проходя через все версии до самой последней версии класса.
При отложенном преобразовании описанная обработка со временем амортизируется в зависимости от того, когда именно к экземплярам был запрошен доступ. Такой подход позволяет внести изменения в схему без перевода базы данных в автономный режим или значительного ущерба для производительности во время модификации. Большинство реализаций, которые поддерживают такой подход, разрешают вносить изменения в схему при работающей системе. Этот фактор особенно важен для приложений, которые требуют высокой степени доступности. На преобразование большой базы данных могут уйти часы и даже дни, а это слишком долго, чтобы оставлять базу данных в автономном состоянии.
Индексы и кластеризация
Модификация схемы может повлиять на индексы и директивы кластеризации в следующих случаях:
при удалении класса, который имеет индекс;
при удалении атрибута, который является компонентом индекса;
при изменении типа атрибута в индексе;
при изменении иерархии наследования, которое приводит к добавлению классов в индекс или удалению их оттуда.
После преобразования схемы и базы данных вам, возможно, придется перестроить или переопределить индекс. При использовании отложенного преобразования вместо немедленного преобразования всех экземпляров, связанных с этим индексом, вполне вероятно выполнение реконструкции индекса.
Часть IV
Ретроспектива
Глава 17
Сравнение технологий объектных и реляционных баз данных
В настоящее время технология реляционных баз данных занимает доминирующее положение на рынке баз данных. Ведущие фирмы успешно продают свои продукты уже в течение более десяти лет. Большой ассортимент программных средств поддержки этой технологии предлагается и от сторонних производителей.
Технология реляционных баз данных была разработана еще до широкого распространения на рынке объектной технологии. С понятием “объект” (первоначально используемым для конструкции класса в языке Simula-67) и принципами построения реляционной модели, предложенными Е.Ф. Коддом (E.F. Codd), компьютерный мир впервые познакомился в конце 60-х годов. И только недавно комитетом ANSI ХЗН2 SQL были сделаны попытки объединить эти две технологии в языке SQL3. Комитет SQL3 должен поддерживать обратную совместимость с существующими SQL- продуктами, что ограничивает его возможность добавлять объекты в язык SQL. Фирмы-производители реляционных баз данных также добавляют собственные расширения в свои SQL-продукты для поддержки объектов. Но означает ли это, что проект сможет по-прежнему использовать реляционную технологию и одновременно “снимать сливки” с обоих подходов? Разработчики должны понимать фундаментальные различия между этими технологиями и предлагаемые ими преимущества.
Если бы производители реляционных баз данных приняли объекты “на вооружение” еще десять лет назад, их технология построения баз данных и связанные с ними приложения были бы сегодня совсем другими, возможно, более высокоорганизованными. Ведь SQL — только один из ряда языков, используемых для интерфейса с реляционной базой данных. Если бы мировое сообщество, связанное с использованием баз данных, раньше приняло объекты, то, возможно, достойное место на рынке занял бы не SQL, а совсем другой язык, более подходящий к работе с объектами.
Какие фундаментальные различия между технологиями объектных и реляционных баз данных? Каким образом разработчики приложений определяют, какая технология лучше подходит к их приложениям? В этой главе делается попытка ответить на поставленные вопросы, а также рассматриваются принципы поддержки объектов в языке SQL3.
Две упомянутые здесь технологии предназначены для удовлетворения различных требований приложений. Многие менеджеры разработки при выборе технологии отдают предпочтенйе SQL и реляционным базам данных, благодаря их “зрелому возрасту”, доминирующему положению на рынке и высокому уровню стандартизации. Такое решение будет вполне оправдано для определенных требований приложений, но в других случаях было бы лучше выбрать объектные базы данных.
Модели, системы шипов и языки
Главные различия между объектными и реляционными базами данных сконцентрированы в моделях, системах типов и языках, а также в характере использования этих элементов в среде клиент/сервер. Выбор этих трех компонентов существенно влияет на информационную модель приложения и ее представление в архитектуре программного продукта.
Реляционная модель и среда
Реляционная модель базируется на понятии “таблица”. Таблица определяется для каждой сущности, моделируемой приложением. Она состоит из строк, и каждая строка таблицы соответствует некоторому экземпляру сущности. Таблица также имеет набор столбцов, которые соответствуют атрибутам моделируемой сущности. Каждый столбец характеризуется определенным типом данных, именуемым доменом. На пересечении строки и столбца находится ячейка. Каждая ячейка содержит либо некоторое значение домена, либо null-значение. .
Реализация реляционной базы данных обеспечивает возможность использования в качестве доменов определенных типов данных. Система доменных типов строго фиксирована: поддерживаются только типы, предоставленные фирмой-производителем данного продукта. Набор операций, который может быть выполнен над экземпляром домена, определяется языком SQL и конкретной реализацией базы данных. Схема базы данных представлена в первой нормальной форме, если каждый домен является атомарным, т.е. он не может содержать коллекцию значений. Элемент коллекции должен быть представлен строкой таблицы.
Таблица — это единственный тип данных, который может определить приложение. Информационная модель приложения преобразуется в форму таблиц. Но операции, которые могут быть выполнены над таблицей, определяются реляционной моделью и языком SQL. Взаимодействие с содержимым базы данных реализуется на языке SQL. К примитивным операциям чтения относятся извлечение нужных строк (select), извлечение нужных столбцов (project) и соединение таблиц (join). Между таблицами можно динамически установить связи, задав условие соединения, определяющее ограничения, которым должны отвечать столбцы этих таблиц. Для извлечения нужных строк таблицы в директиве where задаются булевы предикаты. Для формирования конечного результата запроса используются выражения в директиве select, благодаря которым из столбцов таблицы извлекаются нужные значения.
Представление и функционирование реляционной системы типов полностью сконцентрированы на сервере базы данных. С точки зрения реляционной базы данных существует одна модель, одна система типов и один язык, который “диктует все условия” работы. Приложения должны признать эту модель и обеспечить преобразования между их представлением и представлением базы данных.
Язык SQL используется для выражения действий, выполняемых на сервере реляционной базы данных. С помощью SQL можно выразить довольно сложные условия выделения данных, подвергаемых нужной обработке. Приложение может управлять количеством данных, которое должно быть отправлено со стороны сервера приложению. Благодаря SQL можно полностью выразить простые транзакции и выполнить их на сервере реляционной базы данных. Данные могут обновляться без необходимости выполнения “кругосветного путешествия” от сервера к клиенту, благодаря обновлениям, распространяемым от клиента назад к серверу.
Приложение в реляционной среде
Приложение обычно пишется на одном из таких языков программирования третьего поколения (.third-generation programming /anguage — 3GL), как С, C++, Cobol или Fortran. В некоторых случаях может быть также использован язык программирования более высокого уровня (четвертого поколения — 4GL), а именно PowerBuilder, который предоставляет быстродействующую среду макетирования (rapid prototyping) для построения интерфейса с базой данных на основе набора форм. Типичная 4GL- система типов схожа с системой типов базы данных, что обеспечивает высокий уровень интеграции для простых приложений. Однако приложения, требующие более высокоорганизованного функционирования, должны быть написаны на языке третьего поколения (3GL).
Приложение и реляционная база данных имеют отдельные системы типов. При этом типы данных и возможные действия содержат существенные отличия. Интерфейс между значениями типов столбцов и значениями встроенных типов языка программирования обычно обеспечивает фирма-производитель базы данных. В языке SQL взаимодействие между базой данных и приложением реализуется на базе столбцов, а на уровне таблиц такое отображение не поддерживается. Следовательно, на уровне абстракции сущностей в прикладной области никакого интерфейса не предусмотрено.
В объектной технологии именно типы сущностей являются фокусом моделирования. Реляционные таблицы соответствуют объектам объектной модели, но на этом уровне интерфейс не предоставляется. В результате, объектная модель должна иметь два представления: схему реляционной базы данных и классы приложения. Между этими моделями должно быть определено и реализовано некоторое преобразование. Разработчики и конечные пользователи, которые взаимодействуют с базой данных посредством SQL, должны понимать обе модели и детали преобразования между ними. Помимо описанных сложностей, следует иметь в виду, что для реализации преобразования потребуются значительные усилия разработчиков.
Архитектура реляционной базы данных никак не влияет на представление данных в кэше приложения или управление им. Именно приложение несет ответственность за создание всех экземпляров объектов и отслеживание всех модификаций объектов. Коллекции и связи в объектной модели требуют обработки соединений в таблицах базы данных. Кэш приложения не должен содержать несколько реализаций одного и того же экземпляра. При получении доступа к объекту приложение должно быть уверено, что соответствующие данные еще не были реализованы. При использовании запросов, которые не включают доступ к конкретной строке по ключу, приложение не будет знать, активизирован ли уже объект, пока не получит его из базы данных. В итоге такая организация приводит к существенным расходам ресурсов, причем неоправданным.
Поскольку система типов реляционной базы данных и среда выполнения полностью независимы от языка программирования приложения, реляционная база данных может лучше поддерживать несколько языков приложений, чем объектная база данных. Большинство языков программирования, поддерживаемых реляционными базами данных, не являются объектно-ориентированными. С появлением объектной технологии были разработаны новые языки программирования, которые позволяют разработчикам определять более богатые модели данных и системы типов. Разработчики хотели бы, чтобы семантика их объектных моделей была доступна как в среде приложения, так и в среде базы данных.
Поддержка объектов
В объектной технологии основное внимание уделяется определению абстракций и моделей, а также реализации их в системе типов языка программирования. Для расширения системы типов необходима предпосылка объектной парадигмы. .
Разработчики приложений, которые занимались проектированием сложных информационных моделей, с готовностью приняли объектную технологию благодаря ее способности к снижению уровня сложности. Объекты сложной информационной модели обычно имеют много взаимозависимостей, и приложения в транзакции часто получают доступ к большому числу взаимосвязанных объектов. Необходимые вычисления часто превышают возможности выразительности SQL, и следствием этого является необходимость реализации объектов в приложении. Объектная модель приложения определяется с использованием богатой среды типов объектно-ориентированного языка программирования, а реляционная база данных становится упрощенным вспомогательным механизмом хранения.
Реляционной базе данных не достает конструкций моделирования объектной парадигмы; инкапсуляции, наследования и полиморфизма. Инкапсуляция позволяет задавать множество операций, определяющих весь спектр действий, которые можно выполнить на экземплярах объектов. А в реляционной таблице можно легко получить доступ к каждому значению столбца любой строки. Представление данных не обеспечивается на уровне таблицы, где инкапсуляция часто оказывается весьма необходимой. В прикладной области не могут быть представлены отношения наследования и невозможно использовать полиморфизм. В запросе приложение должно всегда задавать конкретные таблицы. Несмотря на то что конструкции объектного моделирования весьма полезны в приложении, их нельзя использовать в среде реляционной базы данных.
Некоторые приложения используют достаточно простые информационные модели, которые представимы и адекватно управляемы средствами языка SQL. Такие приложения выигрывают от большого выбора программных продуктов сторонних производителей, которые предоставляют достаточно продуктивные среды для построения интерфейсов, чтобы обеспечить хорошую поддержку реляционных баз данных, обслуживающих простые модели данных.
Язык объектной реализации
Определение объекта включает задание множества операций, которые должны быть реализованы в языке. Их реализации являются частью определения типа, а системы типов зависят от конкретного языка. Чтобы напрямую манипулировать экземпляром любого типа, в языке должна быть представлена как система типов, так и реализация типов.
Если некоторая абстракция объекта полностью поддерживается в нескольких языках программирования, необходимые операции должны быть заданы в каждом из этих языков. Более того, языками программирования должны поддерживаться и типы, используемые в объектной реализации. У разработчиков порой не хватает времени и сил на полную реализацию всех объектных абстракций в нескольких языках. Поэтому полное определение типа (т.е. всех его операций) обычно реализуется только в одном языке, а для других языков все ограничивается упрощенным вариантом — лишь представлением данных.
В некоторых ситуациях для дистанционного выполнения операций над объектом, реализованным на сервере в сетевой среде, используется специальная архитектура объектных запросов. Стандарт, описывающий взаимодействие объектов в сетевой среде, известен как CORBA (Common Object Request Broker Architecture). Эта архитектура объектных запросов часто используется в средах, предназначенных для работы многоязычных приложений. В этом случае реализация объектов определяется в каком-то одном языке, а приложениям, написанным на других языках, просто предлагается доступ к интерфейсу для удаленного выполнения операций на объектах.
Сравнение с объектными базами данных в C++
В объектных базах данных основной акцент ставится на тесной интеграции с языком программирования приложения, который используется для определения семантики объектов. Объектные базы данных C++ напрямую поддерживают систему типов С++-приложения. База данных и приложение совместно используют одну систему типов и объектную модель, причем базой данных (как и приложением) поддерживается использование таких конструкций моделирования, как инкапсуляция, наследование и полиморфизм.
При использовании объектной базы данных C++ действия над объектами выполняются только в приложении клиента. Несмотря на то что в некоторых реализациях предусмотрены доменные типы, представления данных в которых позволяют выполнять некоторую обработку на сервере базы данных, эта возможность обычно весьма ограничена. Объектные базы данных C++ имеют “уклон” в сторону клиента, а реляционные — в сторону сервера.
Язык OQL поддерживает абстракции объектной модели, определенные в используемом языке программирования. Язык OQL позволяет включить операции над объектами, предоставляя интерфейс, который соответствует объектной абстракции. Как известно, достоинств без недостатков не бывает, и здесь слабым местом является управление доступом, т.е. закрытые члены класса оказываются очень даже доступными. Другими словами, в среде запросов мы лишаемся инкапсуляции, которой так “гордится” C++. Программы приложения должны обладать способностью формировать запросы с наложением ограничений на атрибуты данных. Поэтому над управлением доступом еще требуется дополнительно поработать, чтобы интерактивный интерфейс запросов мог более точно соответствовать абстракциям, определенным в объектной модели.
Нормализация
Приверженцы реляционных баз данных утверждают, что эта технология превосходит другие благодаря прочному теоретическому фундаменту (в частности, теории нормализации) и опоре на реляционное исчисление. Язык SQL опирается на реляционное исчисление кортежей. Как упоминалось выше (в главе 12), язык OQL базируется на реляционном исчислении доменов, которое представляет собой более общую форму реляционного исчисления, чем может обеспечить SQL.
Теперь несколько слов о реляционных нормальных формах.
Отношение (таблица) представлено в первой нормальной форме (1НФ) тогда и только тогда, когда все составляющие его домены (столбцы) содержат только атомарные значения (т.е. их дальнейшее разложение невозможно).
Отношение (таблица) представлено во второй нормальной форме (2НФ) тогда и только тогда, когда оно представлено в 1НФ и каждый неключевой атрибут полностью определяется первичным ключом.
Отношение (таблица) представлено в третьей нормальной форме (ЗНФ) тогда и только тогда, когда оно представлено во 2НФ и каждый неключевой атрибут нетранзитивно определяется первичным ключом.
Каждый объект должен представлять одну абстракцию. Реализация этой абстракции может включать неатомарные объединения, но объектный интерфейс должен представлять только одну абстракцию. Для представления, значений атомарной абстракции могут использоваться даже объекты, несмотря на то что их внутреннее представление не является атомарным.
В объектной базе данных идентификатор объекта играет роль аналога уникального ключа в реляционной базе данных. Определения второй и третьей нормальных форм затрагивают моделирование отношений атрибутов с соответствующей объектной абстракцией. Эти правила нормализации представляют хорошие принципы моделирования, которые следует применять к проектированию объектных моделей и схем баз данных. Они не являются прерогативой лишь реляционных баз данных и должны применяться также к среде объектных баз данных. Эти принципы не должны нарушаться при проектировании схемы базы данных независимо от вида используемой технологии: реляционной или объектной.
Нормализация часто приводит к модели, в которой для представления сложного объекта приложения нужно соединить много отдельных таблиц. Операция соединения, необходимая для сведения воедино всех связанных строк реляционной базы данных, может отрицательно сказаться на производительности, причем иногда это в большей степени является результатом использования определенных механизмов доступа, чем самого факта нормализации данных. При ощутимом снижении производительности разработчики часто отказываются от нормализованной схемы, чтобы избежать последующих операций соединения, но такой метод может привести к некоторым аномалиям при обновлении данных.
Схема объектной базы данных часто нормализуется в той же степени, что и реляционная база данных. Разработчик схемы объектной базы данных может встроить некоторый объект в качестве члена перманентного объекта вместо использования ссылки на этот объект. Однако приложениям при использовании объектной базы данных по- прежнему нужен доступ к данным и их объединение. Но разница в том, что связи между объектами в объектной базе данных явно определяются на логическом уровне и эффективно реализуются на физическом уровне. Использование объектного идентификатора для ссылки на объект работает быстрее, чем выполнение операции соединения в реляционной базе данных. С увеличением числа таблиц и строк в соединяемых таблицах производительность соединений неуклонно падает, в то время как в объектной базе данных эффективность использования объектных идентификаторов при увеличении количества создаваемых экземпляров остается относительно постоянной.
Доступ к данным
Весь доступ к реляционной базе данных выполняется посредством операторов SQL на основе таблиц. Запрос включает (в качестве начального направления поиска) задание одной или нескольких таблиц в директиве from. Однако директива from определяет декартово произведение. Связи между таблицами устанавливаются путем задания условий соединения, которые накладывают определенные ограничения на значения в столбцах. Директива where выбирает подмножество строк в первоначально заданном декартовом произведении таблиц, которые станут строками таблицы, составляющей конечный результат. Конечный результат запроса также представляет собой таблицу. Выражения в директиве select используются для задания столбцов конечного результата. Директива order by служит для упорядочения строк конечной таблицы, а директива group by предназначена для группирования строк, число которых можно ограничить, например, чтобы конечный результат содержал по одной строке для каждой группы.
В директиве from любого SQL-запроса задается конкретный набор таблиц, что предотвращает полиморфный доступ к экземплярам базы данных. При использовании объектной базы данных ссылки (d_Ref<B>) могут указывать на экземпляры любого класса, производного от общего класса в. Полиморфную ссылку можно встроить в качестве члена некоторого объекта или она может быть элементом коллекции. Как C++, так и OQL могут использовать такую ссылку для доступа к экземпляру, ничего не зная о его реальном типе.
В языке OQL компонентом директивы from может быть любое выражение коллекции (см. раздел 12.3.1). Эта коллекция может содержать ссылки только на небольшую часть экземпляров некоторого класса или даже отдельные объекты, требуемые в запросе. При использовании же SQL стартовой точкой обработки запроса является целая таблица, и кардинальное число таблицы оказывает существенное влияние на производительность запроса. При вычислении булева предиката, заданного в директиве where, количество строк, участвующих в дальнейшей обработке запроса, уменьшается, поэтому при использовании SQL потенциально должен быть обработан гораздо больший набор данных, чем при OQL.
Связи между таблицами устанавливаются динамически на основе определенных значений столбцов в таблицах. При этом выполняется операция, именуемая реляционным соединением. Реляционная модель обладает большой гибкостью в установлении произвольных связей на основе значений столбцов. Исторически сложилось так, что объектные базы данных не обеспечивают такой же уровень гибкости в установлении подобных динамических связей. Язык OQL поддерживает соединения, а некоторые фирмы-производители вместе с' поддержкой соединений также обеспечивают SQL- доступ. Реляционные базы данных опираются на обработку соединений как на единственное средство установки связей, поэтому на оптимизацию соединений было потрачено много ресурсов. Реляционные базы данных могут рассчитывать на более эффективную (по сравнению с объектными базами данных) поддержку обработки соединений. В объектных базах данных основное внимание уделяется обеспечению эффективной поддержки статических связей между объектами. OQL позволяет использовать такие связи в запросе, не требуя никакой дополнительной обработки соединений. Разработчики должны определить, какие связи превалируют в их схеме: статические или динамические. Этот аспект создания модели приложения является существенным фактором в процессе принятия решения о выборе базы данных.
Помимо установления связей, для уменьшения результата запроса можно использовать директиву where, наложив ограничения на значения столбцов. Эта операция называется реляционным извлечением строк. Для того чтобы некоторая строка в декартовом произведении, определяемом директивой from, была включена в конечный результат, должен быть истинным булев предикат, задаваемый в директиве where. Реляционные SQL-реализации позволяют проанализировать потенциально огромный объем данных и посредством ограничений, заданных в запросе, отсеять ненужные данные в сервере базы данных. От сервера к клиенту передается только конечный результат запроса.
Приложение, которое использует навигационный доступ к объектной базе данных, часто оценивает такие условия ограничения в С++-программах управления приложением. Этот подход требует, чтобы данные передавались в адресное пространство клиента. Некоторые реализации выполняют все операции по обработке запросов в процессе приложения клиента. Различные реализации объектных баз данных сильно отличаются одна от другой возможностями и организацией серверной обработки запросов. Если серверная обработка запросов не предусмотрена и такой запрос выполняется для всех экземпляров класса, то каждый экземпляр должен быть передан из сервера базы данных в приложение клиента. Для приложений, которым необходим такой доступ, чрезвычайно важно узнать, насколько большой объем обработки может выполнить та или иная реализация в своем сервере базы данных.
Директива select SQL-запроса определяет столбцы таблицы конечного результата. Эта операция называется реляционным извлечением столбцов. Запрос, по сути, определяет представление, т.е. новую таблицу, выведенную из содержимого одной или нескольких исходных таблиц в базе данных. Результат может быть прямым отображением каждого столбца одной таблицы, но запросы часто используют содержимое нескольких таблиц, а их результаты содержат столбцы, составленные на основе выражений, включающих только подмножество столбцов исходных таблиц. Например, из таблицы Employee запрос может извлечь только столбцы last_name и salary.
Приложение, которое работает с объектной базой данных, для доступа к объекту обычно использует объектную ссылку. В этом случае выполняется операция разыменования, и в кэш-памяти приложения объект активизируется целиком. В памяти приложения C++-определение класса задает “целую” форму экземпляра, а реализация класса не предназначена для работы лишь с некоторым подмножеством его атрибутов.
Реляционные интерфейсы действуют на уровне атрибутов, в то время как объектные базы данных предоставляют интерфейсы на уровне сущностей. Разработчики должны определить, потребуют ли их приложения доступа к целым объектам, или же они должны вычислять представления проекций на основе выражений, включающих лишь некоторое подмножество атрибутов объекта.
В реляционной базе данных индекс используется в нескольких целях. Один или несколько столбцов таблицы обычно служат в качестве первичного ключа, который содержит уникальный идентификатор для строки. Для эффективного просмотра строк индекс обычно размещается на первичном ключе. Внешний ключ моделирует ссылку на строку, а индекс, размещенный на внешнем ключе, позволяет быстро найти строки, которые содержат конкретное значение внешнего ключа. Помимо индексации первичного и внешнего ключей, индексируются также столбцы, которые часто используются в предикатах запросов.
В объектной базе данных для эффективного доступа к связанным объектам используются объектные идентификаторы, связи и коллекции. К индексации прибегают лишь в случаях, когда к объекту осуществляется доступ на основе определенного пользователем идентификатора или значений атрибутов. Индексы в объектных базах данных (по сравнению с реляционными) используются гораздо реже, поскольку здесь реализованы более эффективные навигационные механизмы.
Выполнение
При использовании реляционной базы данных вся обработка выполняется в сервере базы данных, а для клиента приложения никаких действий не предусматривается. Функциональность, реализуемая в сервере базы данных, выражается в SQL- операторах, которые позволяют задать сложные условия выборки данных в виде запросов. Одним словом, реляционную базу данных можно определить как сервер- ориентированную.
Объектная база данных в C++ характеризуется тесной интеграцией с языком программирования и предоставляет возможности для управления кэшем приложения. Многие операции доступа к базе данных инициализируются посредством выполнения операций над объектами, размещенными в кэше клиента. Поэтому объектную базу данных можно назвать клиент-ориентированной.
Некоторые приложения имеют простые информационные модели и требования к транзакциям. Для таких приложений могут оказаться вполне адекватными возможности определения данных и их обработки, предоставляемые средствами SQL. Для других же приложений характерен большой объем обработки, реализуемой на стороне клиента, что может потребовать размещения данных в адресном пространстве приложения. Архитектура конкретного приложения более приемлема либо для клиентского, либо серверного способа обработки. При выборе наиболее подходящей технологии базы данных этот момент нужно обязательно принять во внимание.
Поддержка объектов в SQL3
Комитет ANSI SQL занимается определением SQL3, следующей версии языка SQL. Объектная технология — главная новинка SQL3. Новая спецификация близка к завершению; на момент написания этой книги большинство компонентов этого стан-
дарта находилось на стадии эскизного проекта, включая язык SQL Persistent Stored Modules (PSM), используемый для определения функций (объектных методов). У комитета ANSI SQL3 еще много работы, прежде чем он сможет объявить о завершении работы. Информация в этом разделе основана на спецификации $QL3 эскизного проекта 1996 года.
На проект SQL3 наложено основное условие: он должен оставаться совместимым с ANSI SQL-92, предыдущей версией SQL. Язык “от рождения” обречен на соблюдение следующего ограничения: SQL-запрос получает таблицу на входе и в качестве результата также выдает таблицу. Эта предпосылка служила основным препятствием для поддержки объектов на уровне таблиц (это ограничение обсуждается в разделе 17.4.4).
Зачем же добавлять объекты в SQL? Каким образом их следует добавлять? Сама жизнь (т.е. стремительное расширение спектра областей применения) вынуждает использовать новые типы данных, которые требуют поддержки операций, определяющих их поведение. Для поддержки новых типов данных в приложениях и была “удочерена” объектная технология. Для введения объектов в технологию реляционной базы данных рассматривалось несколько методов. Например, фирмы-производители баз данных могут поддерживать реализации новых типов данных. Этот подход очень ограничен, поскольку даже после такого усовершенствования база данных никогда не сможет удовлетворить требования разных сфер применения. Ведь если нескольким приложениям и потребуется один и тот же тип данных, но каждому из них все же необходима своя семантика. Другой подход состоит в добавлении к реляционной базе данных уровня объектной семантики. В этом случае объектной семантики будет лишен интерфейс языка запросов, что отрицательно скажется на конечной производительности.
Комитет SQL3 нашел еще один способ, который заключается в том, чтобы позволить базе данных расширяться за счет использования типов данных, поддерживаемых пользователем. Такая организация обеспечивает высокий уровень интеграции и расширяемости, а также приемлемую производительность. Среда выполнения SQL включает полную спецификацию и определение объектной семантики и может выполнять операции непосредственно над экземплярами типов, определенных пользователем.
Абстрактные типы данных дня доменов стопбцов
SQL3 позволяет задавать в описании схемы базы данных определенные пользователем абстрактные типы данных (АТД). Тип любого столбца таблицы может относиться к АТД, и каждая ячейка (на пересечении столбца и строки) может содержать АТД-экземпляр. АТД-экземпляры сами по себе не являются перманентными, но приобретают перманентность после размещения в ячейках таблицы. Кроме того, АТД- экземпляры SQL3 не имеют объектных идентификаторов.
Инкапсуляция .
АТД-экземпляры SQL3 поддерживают свойство инкапсуляции. К атрибутам типа разрешен доступ только набору функций, определенных для этого типа. Например, в SQL3 можно определить следующий тип.
create type Address (
streetchar(25),
citychar(20),
statechar (2) ,
zipinteger,
functionis_rural(Address) returns integer
) ;
' Каждый атрибут АТД имеет имя и тип. Имя атрибута должно быть уникальным внутри данного типа, а, его тип — любым существующим типом, включая другие АТД.
Такие атрибуты, как street или city, не доступны напрямую, что обеспечивает определенный уровень инкапсуляции.
Для каждого атрибута автоматически генерируются функции чтения и записи, которые снижают уровень инкапсуляции. Например, в первой строке следующего фрагмента программы для атрибута street типа (точнее, АТД) Address объявляется функция чтения, а во второй — функция записи.
street (Address)returns char(25)
street (Address, char(25))returns Address
Для удобства поддерживаются следующие синтаксические варианты объявлений.
DECLARE a Address;
Запись а. . street эквивалентна записи street (а).
Запись a..street ='1234 Elm Avenue' эквивалентна записи street(a,
'1234 Elm Avenue').
В SQL3 для доступа к атрибуту АТД используется двухточечное обозначение (двухточечная нотация). (Одноточечное обозначение уже использовано для других SQL-механизмов.) Для установки всех атрибутов равными null-значению система генерирует нулевой конструктор.
Наслеуоваиие
АТД-экземпляры SQL3 могут использовать как единичное, так и множественное наследование. Наследуются все хранимые атрибуты и поведение супертипов. Экземпляр подтипа может быть задействован везде, где предполагается использование экземпляра супертипа.
Полиморфизм
SQL3 поддерживает полиморфизм. Подтип может перегружать любую функцию, определенную в супертипе. При вызове перегруженной функции нужный ее вариант определяется типами всех аргументов, а не только первичным аргументом. Такая форма полиморфизма используется в CLOS. Но в таких объектно-ориентированных языках программирования, как C++, Smalltalk и Java, полиморфизм основан на первичном объекте, для которого вызывается операция.
SOL PSM
Язык SQL PSM позволяет разработчикам приложений определить хранимые модули, процедуры и функции, которые доступны в среде SQL. Эти программные единицы могут быть написаны на языке программирования третьего поколения или непосредственно с помощью операторов расширенного варианта SQL.
Функции могут быть реализованы на любом языке программирования третьего поколения, который совместим с SQL (Ada, С, Cobol, Fortran, Pascal и PL/I). Такая организация позволяет SQL усилить существующие библиотеки, но не обеспечивает полностью интегрированную среду. Между SQL и языком программирования приложения существуют нестыковки, которые прямиком ведут к проблемам безопасности и производительности.
SQL PSM также определяет функционально завершенный язык программирования, который полностью интегрирован с языком SQL и обеспечивает полную поддержку для системы типов, доступной в среде SQL. Основной недостаток — отсутствие на данный момент реализаций языка PSM. Этот язык включает следующие (среди прочих) конструкции. .-. ,. ■ ..
call/return
begin/end
if/then/else/endif
case/end case
loop/end loop
while/end while
repeat/until/end
for/end for
Эти средства можно использовать для определения функций, которые выполняются в среде SQL.
SQL PSM поддерживает перегрузку функций: одно и то же имя может быть применено к различным функциям. При анализе вызова функции используются все аргументы. Во время работы подлежащая выполнению функция выбирается на основе наилучшего совпадения реальных типов переданных аргументов. Статическое управление функциями поддерживается только в одном из следующих случаев.
Ни один из аргументов не является АТД-аргументом.
Ни один из АТД-аргументов не имеет подтипов.
Каждой возможной комбинации АТД-аргументов соответствует только одна подходящая функция.
Все АТД-аргументы передаются функции по ссылке.
Именованные шины строк н ссылочные типы
SQL3 позволяет определять именованные типы строк. Именованный тип строк представляет собой последовательность пар, состоящих из имени поля и типа данных. Экземплярами именованных типов строк могут быть либо строки в базовых таблицах, либо представления, либо результаты запросов. Именованные типы строк можно использовать при определении функций, но в этом случае нельзя говорить об инкапсуляции данных. Проверка типов основана на структурной эквивалентности: можно считать, что две различные таблицы имеют одинаковый тип, если идентичны их последовательности пар имен полей и типов данных.
Ссылочные типы позволяют использовать прямые ссылки на экземпляры именованных типов строк. Эти типы аналогичны объектным ссылкам в объектной базе данных. Они строго типизированы, но не поддерживают ссылочных ограничений целостности. Они должны ссылаться на экземпляр в конкретной таблице, что означает отсутствие поддержки полиморфизма. Их можно использовать для задания выражений пути с помощью нового для SQL оператора “стрелка” (->).
select e.last_name
fromEmployee e
wheree,office_mate->address..city = 'Columbus'
Оценка SQL3
Язык SQL3 задуман как добавление объектной поддержки к SQL, но объекты здесь используются только как доменные типы столбцов. Сущности конкретных областей применения баз данных должны по-прежнему отображаться в форме таблиц, без поддержки объектно-ориентированных конструкций: инкапсуляции, наследования и полиморфизма. И только атрибутам сущностей “дозволено” обладать этими свойствами,
что, по всей вероятности, мало приемлемо для тех, кто занимается объектноориентированной разработкой. В современных методологиях объектноориентированного проектирования основное внимание уделяется проектированию объектов для представления сущностей данной предметной области. Атрибуты играют вторичную роль, обычно скрытую под интерфейсом объекта. В таких условиях единственно возможный выход — создавать таблицы с одним столбцом, содержащим АТД- данные. Но тогда не ясно, как реализации справятся с соединениями, выполняемыми с использованием АТД-компонентов в различных таблицах.
SQL3 основан на обобщенной объектной модели, в которой динамическое связывание функций опирается на типы всех аргументов функции, а не на тип одного объектного аргумента. Большинство программистов, занятых объектно-ориентированной разработкой, привыкли иметь дело с общепринятой классической объектной моделью. Форма динамического связывания в SQL, скорее всего, покажется странной для многих разработчиков объектных моделей, которые хорошо знакомы с полиморфизмом, заложенным в большинство объектных языков программирования. Динамическое связывание, основанное на анализе всех аргументов, повышает сложность программ и уменьшает эффективность реализации.
Объекты SQL3 определяются внутри системы типов SQL, а их семантика реализуется в среде SQL. Объекты, определенные в SQL3, не применимы внутри приложения, написанного на таком объектно-ориентированном языке, как C++. Многим группам разработки, использующим C++, представители фирм-производителей реляционных баз данных советовали не покупать продукты объектных баз данных, а подождать реализации SQL3, как раз предназначенной для поддержки объектов. Но пока SQL3 не обеспечивает поддержку объектных моделей C++ лучше, чем это получалось у SQL-92.
SQL3 подходит для приложений, которые написаны на необъектноориентированных языках и для своих данных определяют семантику более высокого уровня, а также требуют, чтобы функциональность была доступна только в среде SQL- сервера. Для некоторых приложений это вполне приемлемо, но далеко не для всех. При использовании SQL3 проблема разногласия между несколькими моделями и системами типов не исчезает.
SQL3 — это стандарт, который находится на стадии разработки. Фирмы- производители реляционных баз данных, не дожидаясь завершения работ над его созданием, выпускают на рынок новые продукты баз данных с собственным вариантом поддержки объектов, который отличается от запланированного в SQL3. Такие продукты называют объектно-реляционными базами данных, причем все эти продукты по- разному поддерживают объекты. В результате рынок реляционных баз данных, скорее всего, потеряет тот уровень стандартизации, которым он “гордился” до сих пор. Аналогичный пробел в стандартизации замедляет расширение спроса и на объектные базы данных. Однако их уровень стандартизации, по сравнению с объектнореляционными продуктами, может значительно повыситься благодаря поддержке стандарта ODMG.
Глава 18
Резюме
Разработчикам, которые предпочитают язык C++ и предполагают работать с базами данных, следует серьезно рассмотреть возможность применения технологии объектных баз данных. Продукты объектных баз данных стали доступны для потребителей с 1987 года. Стандартизацией интерфейсов занимается консорциум ODMG, который постоянно совершенствует объектную технологию.
Затраты на разработку приложений для работы с объектной базой данных существенно ниже расходов на разработку реляционной базы данных. Иными словами, использование объектной базы данных сокращает объем программного кода, который нужно тестировать и поддерживать, а также упрощает другие стадии процесса создания проекта. В будущем это приведет к снижению стоимости проектов и сокращению времени, требуемого на их создание. Следствием общей системы типов и возможности управления кэшем приложения является прозрачный и неинтрузивный интерфейс. Разработчики могут сосредоточиться на определении и реализации своих объектных моделей, а не на различиях между моделирующими возможностями языка приложения и базы данных.
C++ — наиболее распространенный объектно-ориентированный язык программирования. Он также является основным языком, поддерживаемым фирмами- производителями объектных баз данных. Хотя в последнее время у него появился “счастливый соперник” — Java. “Деловые” качества делают этот язык превосходным с точки зрения баз данных. Он решает некоторые проблемы, над которыми в течение ряда лет безуспешно бились разработчики технологий реляционной и объектной баз данных. Он несет в себе “угрозу” нарушить спокойствие в обоих “благородных семействах”. Если ведущие фирмы-производители реляционных баз данных начнут поддерживать Java в средах клиента и сервера, он окажет негативное влияние на принятие стандарта SQL3.
Категории типов
В объектных базах данных используется несколько категорий типов, которые заимствованы из моделирования вида сущность-связь и общей теории баз данных. Типы, представляющие сущности областей применения, поддерживают как перманентность, так и возможность идентификации. Категория доменов представляет самые
примитивные типы, которые служат в качестве типов для атрибутов сущностей. Сущности состоят из набора атрибутов доменного типа. Составные объекты представляют абстракции, требующие объединения объектов сущностей.
Типы в каждой категории содержат объединение компонентов более примитивных типов. При определении типов следует использовать инкапсуляцию, чтобы скрыть сложность таких объединений в реализации типов. Каждый тип должен представлять единую атомарную абстракцию, а инкапсуляцию следует использовать, чтобы скрыть лежащую в ее основе реализацию и обеспечить интерфейс, олицетворяющий эту абстракцию.
Двойные интерфейсы
Объектные базы данных обеспечивают как навигационный, так и декларативный доступ (посредством запросов) к базе данных. В классах C++ есть все, что нужно реализациям баз данных для представления ссылок, коллекций и связей между объектами. Эти классы открывают приложениям бесшовный доступ к объектам, хранимым в базе данных. Доступ к базе данных, реализуемый на более низком уровне, скрыт внутри методов классов, предоставляемых фирмами-производителями баз данных.
В объектных базах данных первоначально не уделялось большого внимания поддержке запросов. Большинство приложений предпочитало использовать эффективный навигационный доступ с помощью средств языка C++. В тех сферах применения, где была принята технология объектных баз данных, сначала использовались более примитивные механизмы файловой системы, в которых не предусмотрено никакой поддержки запросов; поэтому приложения и не рассчитывали на такую поддержку.
Объектные базы данных завоевывают все новые рубежи. Системы, использующие эту технологию, сейчас ориентируются на конечных пользователей, которые привыкли иметь под рукой такой язык запросов, как SQL. Эта ориентация выразилась в разработке высокоорганизованного объектно-ориентированного языка запросов (OQL), который обеспечивает простой синтаксис для задания навигационного доступа на основе статических связей и определения динамических связей посредством предикатов соединений, характерных для SQL-запросов. Хотя С++-приложения в основном используют интерфейс на уровне языка C++, теперь у них есть возможность создания OQL-запросов.
Изменения парадигмы
С появлением технологии объектных баз данных проектировщики схем и разработчики приложений столкнулись с изменениями парадигмы, используемой ими до сих пор. И хотя некоторые из этих изменений требуют от разработчиков фундаментального пересмотра способа взаимодействия с базами данных, в целом они существенно уменьшают сложность разработки.
Тем, кто незнаком с объектной технологией, нужно научиться моделировать любую предметную область с помощью объектов. Однако многие разработчики, которые решили использовать объектную базу данных в среде C++, вероятно, уже знакомы с объектным моделированием в C++ и не будут испытывать больших трудностей при таком изменении технологии. Скорее наоборот, они вскоре поймут, что в этом случае проблем с моделированием становится меньше, чем при использовании реляционных баз данных.
Если вам доводилось создавать С++-приложения для работы с реляционными базами данных, то общая система типов окажется именно тем изменением' в парадигме, которое будет воспринято вами “с распростертыми объятиями”, поскольку оно уменьшает общую сложность системной архитектуры. Более того, переход к новой технологии по-
зволяет для всех приложений применять одну и ту же объектную модель, что повышает степень многократного использования объектов, а это, в свою очередь, снижает стоимость разработки. При создании приложений для реляционной базы данных разработчики часто работают независимо друг от друга, что приводит к избыточной разработке одних и тех же объектов. Поэтому использование базой данных и приложением общей объектной модели является более экономным вариантом организации работы.
Другое изменение парадигмы состоит в том, что базы данных при такой организации обретают возможность управлять кэшем приложения. Механизм управления кэшем и общая система типов составляют более “крупное” изменение парадигмы, которое определяется как прозрачность базы данных. Управление кэшем приложения приводит к существенному снижению стоимости разработки. При использовании реляционной базы данных значительный объем программного кода часто посвящен именно преобразованию объектов базы данных из одного представления в другое и обратно. Кроме того, объектная база данных сама заботится об автоматической записи модифицированных объектов обратно в базу данных при завершении транзакции, а ведь при написании приложения для реляционной базы данных эту часть работы должен собственноручно реализовать программист. Хотя и существуют средства, обеспечивающие эти возможности на уровне реляционной базы данных, пользователь языка запросов должен при стыковке приложения и базы данных использовать представление данных более низкого уровня.
В реляционных базах данных “мозговой центр” располагается на стороне сервера и используются данные, тип которых ограничен только фиксированным набором простых типов. Однако объекты, наряду с данными, включают и операции, образуя тем самым новый уровень сложности реализации. Эти операции должны быть реализованы в конкретном языке программирования, что уменьшает возможность эффективной поддержки нескольких языков программирования. Такая особенность языка C++, как использование указателей для непосредственного обращения к памяти, привела к тому, что “мозговой центр” объектных баз данных сместился в сторону клиента. Производители баз данных не склонны позволять С++-программам приложений “хозяйничать” в их серверах.
18 Л. Стандартизация
На уровень признания потребителями объектных баз данных повлияло несколько факторов. Ведущие фирмы-производители объектных баз данных довольно невелики по сравнению со своими “реляционными” соперниками. Каждый производитель объектной базы данных имеет уникальный интерфейс, что, безусловно, не способствует той степени переносимости, которую можно было бы достичь, опираясь на SQL- интерфейс реляционных баз данных. Кроме того, между продуктами объектных баз данных имеются и существенные архитектурные различия.
Рынок объектных баз данных отличается высокой конкуренцией. Чтобы убедить покупателей в превосходстве своего продукта, многие фирмы “открывали глаза” покупателям на фундаментальные дефекты в продуктах конкурентов. Такая стратегия наносила вред не только противникам, но и всей технологии в целом, что снижало уровень ее признания на рынке. Когда пользователи обсуждали продукты разных производителей, то оказывалось, что им больше известно о недостатках, чем о достоинствах, а это, безусловно, не способствовало их доверию к новой технологии и мешало принятию правильного решения. Чтобы сохранить свои позиции на рынке, производители реляционных баз данных, в свою очередь, критиковали и продолжают критиковать возможности новой технологии. Поэтому менеджеры разработок часто приходят к выводу, что слишком рискованно делать выбор в пользу объектной технологии вообще и в пользу определенного производителя объектной базы данных в частности.
Консорциум производителей и пользователей объектных баз данных (Object Database Management Group — ODMG) определил начальный стандарт для взаимодействия с объектными базами данных. В этой книге описаны интерфейсы ODMG C++ и OQL. Стандартизация интерфейсов, поддерживаемых фирмами- производителями, обеспечивает определенную степень переносимости приложений. Благодаря ожидаемой стандартизации, рыночный интерес к продуктам объектных баз данных, по утверждению их производителей, значительно возрос.
При выборе конкретной реализации объектной базы данных разработчики приложений должны знать, поддерживает ли она стандарт ODMG. Разработчикам следует определиться в составе необходимых им компонентов и убедиться в том, что покупаемый продукт способен их предоставить. Например, те, кто пишет С++-приложения, должны требовать С++-поддержки. Те, для кого важен доступ к базе данных посредством запросов, должны требовать поддержки языка объектных запросов OQL.
Некоторые объектные базы данных поддерживают SQL, что позволяет им воспользоваться средствами, созданными сторонними производителями на основе языка SQL и стандарта ODBC (Open DataBase Connectivity). Однако не следует забывать, что схемы баз данных определяются в среде C++, а не SQL. Да и преобразование объектных моделей в SQL в различных реализациях объектных баз данных выполняется по-разному, что отнюдь не способствует переносимости запросов для одинаковых моделей С++- объектов. Следовательно, использование SQL не предполагает переносимости.
Язык объектных запросов OQL опирается на ту объектную модель ODMG, которая используется в среде C++, поэтому OQL обеспечит более высокий уровень переносимости между реализациями ODMG, чем SQL.
Объектные в реляционные базы данных
Объектные и реляционные базы данных предназначены для удовлетворения различных требований приложений, и поэтому, как те, так и другие, продолжают играть свою роль на рынке. Объектные базы данных тесно интегрированы с объектноориентированным языком программирования, что обеспечивает для приложения и базы данных общую объектную модель и систему типов. Реляционные базы данных не зависят от языка программирования приложения и предоставляют интерфейс и систему типов, которые способны с одинаковой эффективностью поддерживать приложения, написанные на разных языках программирования. Несмотря на объектную “оснастку” новой версии SQL, SQL3, система объектных типов, используемая в среде базы данных, будет отличаться от системы типов приложения и совершенно отделенной от нее.
Объектные базы данных более эффективно поддерживают статические связи, которые используются для навигации между взаимосвязанными объектами. Реляционные базы данных выгодно отличаются спецификацией динамических связей, использующих условия соединения таблиц на основе значений столбцов. Поэтому, прежде чем остановить свой выбор на той или иной технологии, разработчики приложений должны четко представлять требования, предъявляемые их приложениями к модели данных и организации взаимодействия различных компонентов.
Взгляд в бцдущев
1996 год стал поворотным пунктом в объектной поддержке баз данных. В это время были выпущены начальные реализации стандарта ODMG-93. Две ведущие фирмы- производители объектных баз данных публично заявили о своем намерении поддержать этот стандарт. В результате у них увеличился объем продаж и поднялся престиж
на рынке. Можно сказать, что рынок “повернулся лицом’’ к технологии объектных баз данных. В 1996 году комитет ANSI SQL выразил свое намерение стандартизировать SQL-поддержку объектов.
Предполагается, что в ближайшие годы станет заметной тенденция взаимопроникновения обеих технологий баз данных. Каждая из них попытается вобрать в себя лучшие качества другой. Реляционные базы данных добавят объектные возможности, возможно, сделав акцент на серверной поддержке объектов и вместе с тем “оживив” обработку данных на стороне клиента. Фирмы-производители объектных баз данных постараются повысить уровень стандартизации и поддержки доступа посредством языка запросов. Не исключено, что такое развитие событий может затруднить для покупателя процесс принятия решения о выборе продукта.
Java
По всей вероятности, язык программирования Java окажет огромное влияние на дальнейшее развитие технологий реляционных и объектных баз данных. Он достиг беспрецедентного уровня по темпам признания в компьютерной индустрии. Этот язык получил высокую оценку даже со стороны стойких приверженцев таких конкурирующих языков программирования, как C++ и Smalltalk. Даже библиотеки для этого языка были разработаны просто-таки в рекордные строки. Известно немало случаев, когда производители программных продуктов пересматривали политику вложения средств в С++-разработки.
JavaSoft, филиал компании Sun Microsystems, на который возложена ответственность за разработку языка Java и библиотек для него, определил Java-интерфейс, именуемый JDBC. Он смоделирован по аналогии с интерфейсом ODBC, но с более высоким уровнем интеграции с языком Java. Если интерфейс ODBC обеспечивает SQL- доступ к базе данных на уровне столбцов (атрибутов), то некоторые фирмы уже разрабатывают Java-ориентированный доступ к реляционным базам данных на уровне объектов (сущностей). В версии 2.0 стандарта ODMG предусмотрено использование языка Java, и фирмы-производители объектных баз данных уже предоставляют Java- интерфейсы. Некоторые производители реляционных баз данных для поддержки объектной семантики в своих SQL-средах рассматривают возможность использования Java вместо языка SQL3 PSM (или наряду с ним). Java имеет много преимуществ по сравнению с языком SQL3 PSM, ведь Java — это язык программирования общего назначения, который всегда будет иметь более сильную промышленную поддержку, чем язык, ориентированный только на управление базами данных.
Фирмы-производители реляционных баз данных уже предлагают объектные решения, которые не опираются на SQL3. Например, некоторые фирмы только предполагают использовать Java в качестве языка сервера базы данных, а есть и такие, которые уже объявили о такой поддержке. Рынок реляционных баз данных, облюбовавший Java в своем стремлении “оседлать” объекты, рискует раздробиться, ведя поиски в различных направлениях, что не может не отразиться (конечно, негативно) сначала на уровне стандартизации, а затем (как следствие) на признании потребителей.
Язык Java подкупает многими моментами, весьма привлекательными для среды базы данных. Он обеспечивает независимость от платформы и требует минимальных расходов, связанных с инсталляцией и администрированием. Основной отличительной особенностью Java является отсутствие указателей, поэтому Java-приложения не могут получать прямой доступ к памяти. Исходный код Java транслируется в так называемый байт-код, который затем интерпретируется виртуальной машиной (Java Virtual Machine — JVM). Таким образом, Java-приложение (аплет) может быть выполнено на любой платформе, для которой реализован интерпретатор системы команд JVM (т.е. виртуальная машина является приложением соответствующей операционной
системы). Когда JVM динамически встраивает некоторый класс в среду, она выполняет специальную проверку, чтобы убедиться в том, что данная программа не может напрямую обращаться к памяти или внешним ресурсам, если у нее на это нет разрешения. Такая организация обеспечивает более высокий уровень ошибкоустойчивости и безопасности, чем C++. -
Фирмы-производители баз данных более доброжелательно настроены к внедрению виртуальной машины Java и выполнению байт-кода Java в своих процессах серверов, чем к использованию C++. Производители баз данных обеспокоены тем, что “блуждающие” С++-указатели могут испортить память сервера, и позволяют объектам С++-приложений осуществлять свои “естественные потребности" только в адресном пространстве процесса приложения клиента.
Производители баз данных, которые в своих серверах поддерживают обработку запросов только в некоторой степени, ограничивают функциональные возможности до уровня обработки примитивных типов. Семантика объектов приложения существует только в среде клиента, что уменьшает бесшовность поддержки типов в среде базы данных. Это сводит на нет выигрыш в производительности, который мог быть достигнут, если бы семантика была включена в ограничивающие условия запросов, оцениваемые в сервере. В результате клиенту передается больше объектов, чем необходимо.
Любая база данных- могла бы содержать в своем сервере реализацию виртуальной машины Java, где классы Java выполнялись бы без риска искажения памяти. В сервере базы данных и приложении клиента можно использовать одни и те же типы языка Java. Приложения, написанные на языке Java, и запросы, созданные на любом языке запросов (SQL или OQL), могли бы разделять единую систему типов и рассчитывать на полноценное функционирование объектов. Система типов в этом случае не была бы ориентирована только на клиента или только на сервер: она бесшовно работала бы в обеих средах. В результате приложение могло более эффективно использовать обе среды выполнения. Производители объектных и реляционных баз данных планируют использовать Java в качестве языка сервера, и тогда выполнение классов Java будет возможно как в среде клиента, так и в среде сервера.
Несмотря на беспрецедентное признание на рынке языка Java, потребуется еще ни один год, чтобы проекты по его использованию в реализациях технологий баз данных превратились в реальность. Язык C++ еще долгое время будет оставаться лучшим вариантом для тех приложений, которые требуют высокой производительности, поскольку существующие в настоящее время JIT-компиляторы (/ust-m-rime) Java генерируют объектный код, который работает медленнее по сравнению с С++-кодом (уборка мусора обычно происходит медленнее, чем удаление в явном виде). Преимущество в производительности объектных баз данных C++ явилось ключевым фактором в принятии этой технологии такими сферами, как системы CAD/CAM, CASE и телекоммуникационные службы. C++ будет еще долгое время занимать ведущее положение на рынке высокопроизводительных систем и, несомненно, завоюет новые области применения.
Часть V
Приложения
Приложение А
Пример схемы
Объектная модель, показанная на рис. А.1, иллюстрирует схему, используемую в данной книге. Между классами Person и Employee существует связь типа IS-A.
class ostream;class istream;struct Address {class ostream;class istream;struct Address {d_String d_String char charfriend ostream & );d_String d_String char charfriend ostream & );
class ostream;
class istream;
struct Address {
class ostream;
class istream;
struct Address {
d_String d_String char char
friend ostream & );
d_String d_String char char
friend ostream & );
Рис. А.1. Пример схемы
Схему базы данных представляет следующий заголовочный С++-файл.
Address(const char *s, const char *c, const char *st, const char *z);
Address();
Address(const struct Address 6);
street;
city;
state[ 3 ] ;
zip_code[6];
operator« (ostream &, const Address s);
class Person : public d_Object { public:class Person : public d_Object { public:const d_String & const d_String & const struct Address void d_Date d_Ushort void void friend ostream & private:d_String d_String Address char d_Date d_Ref<Person> d Set<d Ref<Person> >const d_String & const d_String & const struct Address void d_Date d_Ushort void void friend ostream & private:d_String d_String Address char d_Date d_Ref<Person> d Set<d Ref<Person> >Person(const char *fn, const char *ln, const Address &, const d_Date &, const char *phone);
class Person : public d_Object { public:
class Person : public d_Object { public:
const d_String & const d_String & const struct Address void d_Date d_Ushort void void friend ostream & private:
d_String d_String Address char d_Date d_Ref<Person> d Set<d Ref<Person> >
const d_String & const d_String & const struct Address void d_Date d_Ushort void void friend ostream & private:
d_String d_String Address char d_Date d_Ref<Person> d Set<d Ref<Person> >
Person(const char *fn, const char *ln) ;
-Person();
first_name() const;
last_name() const; address() const;
newAddress(const struct Address &); birth_date() const; age() const;
deactivate();
d_deactivate();
operator<<(ostream s, const Person s) ;
_fname;
_lname;
_address;,
_phone_number[11) ;
_birth_date;
_spouse;
children;
class Employee : public Person { public:
Employee(const char *fn, const char *ln, const Address s, const d_Date &, const char *phone, const d_Ref<Department>&d, const char *ophone, const char *email, d_ULong sal);
Employee(const char ‘fn, const char *ln);
-Employee();
d_Ref<Employee>office_mate ;
d_Ref<Department>dept;
d_Set<d_Ref<Project> >projects;
const char *office_phone() const;
d_Ulongsalary() const;
voiddeactivate();
voidd_deactivate();
static const char * constextent_name;
static d_Set<d_Ref<Employee> >* Employees();
friend ostream &operator<<(oscream &, const Employee &);
static d_Ref<Employee>create_from_stream(istream &,
const d_Ref<Department> s); private: static d_Set<d_Ref<Employee> > *_extent; char_office_phone[11] ;
d_String_email_address;
d_Ulong_salary;
};
d_Object {d_Object {
d_Object {
d_Object {
class Department : public public:
const d_String s d_Set<d_Ref<Employee> > d_List<d_Ref<Project>
Department(const char *n, const char *fax) ;
-Department();
name!) const;
employees;
projects;
void
Employee *
addEmployees(istreams); employee(const char *fn, const char *ln) const;
Project * Project * int
project(const char *pn) const;
addProject(const char *pn, d_ULong cost); assign(const char *fn, const char *ln, const char *pn);
Employee * void void void void
manager() const;
assignManager(Employee *); assignsecretary(Employee *); printEmployees(ostream S); printprojects(oscream s) ;
static d_Ref<d_Set<d_Ref<Department> > > depts();
friend ostream s private:
operator<<(ostream &, const Departments);
d_String
d_Ref<Employee> d_Ref<Employee> d_Ulong char
_name;
_manager;
_secretary;
budget;
fax[11];
static d_Ref<d_Set<d_Ref<Department> > > _depts;
static const char * const
depts_name;
class Process : public d_Object public:
d String
) ;
name ;
class ProjectProcessStep public:
: public d_Object {
ProjectProcessStep();
ProjectProcessStep(
const ProjectProcessStep &);
'ProjectProcessStep();
ProjectProcessSteps void void d_Ref<Process> d_Ref<Ernployee> d_Date d_Date d Ulong
);
operator=(const ProjectProcessStep &) ; d_activate();
d_deactivate ();
process;
owner;
projectedCompletion;
actualcompletion;
staffHours;
class Project : public d_Object {
public:
Project(const char *pname, const d_Ref<DeparCment> &, d_ULong c) ;
void void d_String d_Ref<Department> d_Set<d_ReJ<Employee>
d_activate() ; d_deactivate();
name;
dept;
>members ;
d_List<d_Ref<ProjectProcessStep> > processes;
d_Date d_ULong friend ostream s
due_date;
cost;
operator<<(ostream &, const Project 6);
Приложение Б
Классы ODMG C++
В данном приложении содержится полное описание классов стандарта ODMG 2.0 и их функций-членов. Метаклассы, включенные в ODMG 2.0, приведены отдельно в приложении В. Эти приложения можно использовать в качестве справочника как для этой книги, так и любой реализации стандарта ODMG. К сожалению, спецификация ODMG 2.0 не включает полное описание интерфейса. Я внес предложение о добавлении этого материала в спецификацию ODMG, но члены группы ODMG посчитали, что стиль справочника не соответствует содержанию книги, посвященной ODMG. Поэтому справочный материал вынесен в приложения.
Булевы значения
Многие функции в интерфейсе ODMG возвращают истинные или ложные значения булева типа. На момент выпуска первой версии стандарта ODMG-93 тип данных bool еще не был элементом языка C++, да и сейчас нельзя с уверенностью говорить о его повсеместном распространении в С++-средах. В версии ODMG-93 булевы функции возвращают значения типа int: 0 (ложь) и ненулевое (истина). В версии 2.0 стандарта ODMG интерфейс уже оснащен типом d_Boolean, и для представления истинного и ложного значений определены символы d_True и d_False. Справочная часть настоящей книги соответствует интерфейсу версии 2.0. Поэтому, если вы используете реализацию версии 1.2, необходимо позаботиться о том, чтобы соответствующие булевы значения имели тип int. Но при переходе к версии 2.0 (в целях совместимости) в вашем приложении должны быть определены символы d_True и d_False типа d_Boolean.
Имя: символы препроцессора
В версии ODMG 1.2 определен символ препроцессораODMG 93, который
можно использовать для условной компиляции. В версии ODMG 2.0 определен следующий символ.
#define _ _ODMG_ _ 20
Значение этого символа соответствует версии стандарта ODMG данной реализации.
КоллекцииКоллекцииd_Collection<T>d_Collection<T>d_Iterator<T> .d_Iterator<T> .d_Set<T> d_Bag<T> d_List<T>d_Set<T> d_Bag<T> d_List<T>d_Varray<T>d_Varray<T>d_Ref<T>d_Ref<T>
Коллекции
Коллекции
d_Collection<T>
d_Collection<T>
d_Iterator<T> .
d_Iterator<T> .
d_Set<T> d_Bag<T> d_List<T>
d_Set<T> d_Bag<T> d_List<T>
d_Varray<T>
d_Varray<T>
d_Ref<T>
d_Ref<T>
Имя: все классы
На рис. Б.1 показаны отношения наследования, которые существуют между классами ODMG (они обозначены линиями), и категории, которым принадлежат эти классы и другие типы (они сгруппированы в “именованные” прямоугольники с закругленными углами).
Ссылки
d_Objectd_Objectd_Ref_Any
d_Object
d_Object
d_Dictionary<K,V>
d_Rel_Set<T,MT>d_Rel_Set<T,MT>d_Rel_List<T,MT>Связиd_Rel_List<T,MT>Связиd_Rel_Ref<T>d_Rel_Ref<T>
d_Rel_Set<T,MT>
d_Rel_Set<T,MT>
d_Rel_List<T,MT>
Связи
d_Rel_List<T,MT>
Связи
d_Rel_Ref<T>
d_Rel_Ref<T>
'd_String
:Домены, связанные с
J исчислением времени
.d_Date
Домены■,,
;d_Database
■d_Booleani,
\jd_Extent<T>
•d_Chard_Octet j
j'd_Transaction
•d_Interval
d_Shortd_Ushort
;:d_Error
I (d_Time
!d_Longd_Ulong ,
j Кd_Timestamp
Id_OQL_Query
sd_Floatd_Double
...—
..,...,
Рис. Б.1. Классы ODMG
Неупорядоченная коллекция (мультимножество) элементов типа т, которая допускает повторяющиеся значения. Операции объединения, пересечения и вычитания описаны в разделе 7.4.2.
Синопсис
template <class Т> class public:template <class Т> class public:d_Bag : public d_Collection<T> {d_Bag : public d_Collection<T> {d_Bag<T> & unsigned long d_Bag<T> &d_Bag<T> & unsigned long d_Bag<T> &d_Bag<T> & d_Bag<T> & d_Bag<T>d_Bag<T> & d_Bag<T> & d_Bag<T>friend d_Bag<T>friend d_Bag<T>d_Bag<T> &d_Bag<T> &d_Bag<T> & d_Bag<T> & d_Bag<T> friend d_Bag<T>d_Bag<T> & d_Bag<T> & d_Bag<T> friend d_Bag<T>d_Bag<T> &d_Bag<T> & d_Bag<T> & d_Bag<T> friend d_Bag<T>d_Bag<T> &d_Bag<T> & d_Bag<T> & d_Bag<T> friend d_Bag<T>d_Bag();d_Bag(const d_Bag<T> &b2);~d_Bag();operator=(const d_Bag<T> &b2);occurrences of(const T &val) const;union_of(const d_Bag<T> &bl,const d_Bag<T> &b2);union_with(const d_Bag<T> &b2);operator+=(const d_Bag<T> &b2);create_union(const d_Bag<T> &b2) const;operatorf(const d_Bag<T> &Ы,const d_Bag<T> &b2); intersection_of(const d_Bag<T> &bl,const d_Bag<T> &b2);intersection_with(const d_Bag<T> &b2);operator*»(const d_Bag<T> &b2);create_intersection(const d_Bag<T> &b2) const; operator* (const d_Bag<T> &Ы,const d_Bag<T> &b2) ;difference_of (const d_Bag<T> &Ы,const d_Bag<T> &b2);difference_with(const d_Bag<T> &b2);operator-»(const d_Bag<T> &b2);create_difference.(const d_Bag<T> Sb2) const; operator-(const d_Bag<T> &bl,const d_Bag<T> Sb2);d_Bag();d_Bag(const d_Bag<T> &b2);~d_Bag();operator=(const d_Bag<T> &b2);occurrences of(const T &val) const;union_of(const d_Bag<T> &bl,const d_Bag<T> &b2);union_with(const d_Bag<T> &b2);operator+=(const d_Bag<T> &b2);create_union(const d_Bag<T> &b2) const;operatorf(const d_Bag<T> &Ы,const d_Bag<T> &b2); intersection_of(const d_Bag<T> &bl,const d_Bag<T> &b2);intersection_with(const d_Bag<T> &b2);operator*»(const d_Bag<T> &b2);create_intersection(const d_Bag<T> &b2) const; operator* (const d_Bag<T> &Ы,const d_Bag<T> &b2) ;difference_of (const d_Bag<T> &Ы,const d_Bag<T> &b2);difference_with(const d_Bag<T> &b2);operator-»(const d_Bag<T> &b2);create_difference.(const d_Bag<T> Sb2) const; operator-(const d_Bag<T> &bl,const d_Bag<T> Sb2);
template <class Т> class public:
template <class Т> class public:
d_Bag : public d_Collection<T> {
d_Bag : public d_Collection<T> {
d_Bag<T> & unsigned long d_Bag<T> &
d_Bag<T> & unsigned long d_Bag<T> &
d_Bag<T> & d_Bag<T> & d_Bag<T>
d_Bag<T> & d_Bag<T> & d_Bag<T>
friend d_Bag<T>
friend d_Bag<T>
d_Bag<T> &
d_Bag<T> &
d_Bag<T> & d_Bag<T> & d_Bag<T> friend d_Bag<T>
d_Bag<T> & d_Bag<T> & d_Bag<T> friend d_Bag<T>
d_Bag<T> &
d_Bag<T> & d_Bag<T> & d_Bag<T> friend d_Bag<T>
d_Bag<T> &
d_Bag<T> & d_Bag<T> & d_Bag<T> friend d_Bag<T>
d_Bag();
d_Bag(const d_Bag<T> &b2);
~d_Bag();
operator=(const d_Bag<T> &b2);
occurrences of(const T &val) const;
union_of(const d_Bag<T> &bl,
const d_Bag<T> &b2);
union_with(const d_Bag<T> &b2);
operator+=(const d_Bag<T> &b2);
create_union(const d_Bag<T> &b2) const;
operatorf(const d_Bag<T> &Ы,
const d_Bag<T> &b2); intersection_of(const d_Bag<T> &bl,
const d_Bag<T> &b2);
intersection_with(const d_Bag<T> &b2);
operator*»(const d_Bag<T> &b2);
create_intersection(const d_Bag<T> &b2) const; operator* (const d_Bag<T> &Ы,
const d_Bag<T> &b2) ;
difference_of (const d_Bag<T> &Ы,
const d_Bag<T> &b2);
difference_with(const d_Bag<T> &b2);
operator-»(const d_Bag<T> &b2);
create_difference.(const d_Bag<T> Sb2) const; operator-(const d_Bag<T> &bl,
const d_Bag<T> Sb2);
d_Bag();
d_Bag(const d_Bag<T> &b2);
~d_Bag();
operator=(const d_Bag<T> &b2);
occurrences of(const T &val) const;
union_of(const d_Bag<T> &bl,
const d_Bag<T> &b2);
union_with(const d_Bag<T> &b2);
operator+=(const d_Bag<T> &b2);
create_union(const d_Bag<T> &b2) const;
operatorf(const d_Bag<T> &Ы,
const d_Bag<T> &b2); intersection_of(const d_Bag<T> &bl,
const d_Bag<T> &b2);
intersection_with(const d_Bag<T> &b2);
operator*»(const d_Bag<T> &b2);
create_intersection(const d_Bag<T> &b2) const; operator* (const d_Bag<T> &Ы,
const d_Bag<T> &b2) ;
difference_of (const d_Bag<T> &Ы,
const d_Bag<T> &b2);
difference_with(const d_Bag<T> &b2);
operator-»(const d_Bag<T> &b2);
create_difference.(const d_Bag<T> Sb2) const; operator-(const d_Bag<T> &bl,
const d_Bag<T> Sb2);
Описание
d_Bag ()
Нулевой конструктор, который инициализирует мультимножество без элементов.
d_Bag(const d_Bag<T> &Ь2)
Конструктор копии, который инициализирует новое мультимножество путем копирования элементов мультимножества, адресуемого ссылкой Ь2. Для непримитивных типов вызывается конструктор копии класса т.
~d_Bag()
Деструктор класса удаляет мультимножество. При использовании непримитивных типов вызывается деструктор класса т для каждого элемента этого мультимножества.
d_Bag<T> &
operator=(const d_Bag<T> &Ь2)
Оператор присваивает содержимое одного мультимножества (Ь2) другому. Он возвращает ссылку на объект, разрешая каскадное выполнение операций присваивания. При попытке присвоить мультимножество самому себе никакой операции присваивания не выполняется. Если до выполнения операции мультимножество, которому присваивается мультимножество Ь2, содержало какие- то элементы, они будут удалены. В результате объект будет содержать копию элементов мультимножества, адресуемого ссылкой Ь2.
unsigned long
occurrences_of(const T &val) const
Возвращается количество элементов в мультимножестве, которые равны значению val.
d_Bag<T> &
union_of(const d_Bag<T> &Ы, const d_Bag<T> &b2)
Создается объединение аргументов функции Ы и Ь2, которое размещается в объекте результата. Исходные элементы конечного объекта мультимножества удаляются до начала операции, и для каждого из них вызывается деструктор (если таковой существует). Функция возвращает ссылку на этот объект. Ниже приведен пример синтаксиса.
bag.union_of(bagl, bag2);
d_Bag<T> &
union_with(const d_Bag<T> &b2)
Создается объединение из элементов данного объекта мультимножества и мультимножества Ь2. Результат объединения помещается в исходный объект. Функция возвращает ссылку на этот объект. Ниже приведен пример синтаксиса.
bag.union_with(bag2);
d_Bag<T> &
operator+=(const d_Bag<T> &b2)
Эта функция выполняет ту же операцию, что и предыдущая функция union_with. Вычисляется объединение данного объекта и мультимножества Ь2, а результат размещается в исходном объекте. Функция возвращает ссылку на этот объект. Ниже приведен пример синтаксиса.
bag += bag2 ;
d_Bag<T>
create_union(const d_Bag<T> &b2) const
Создается объединение из элементов данного объекта мультимножества и мультимножества Ь2. Исходный объект при этом не изменяется, а результат представляет собой новый объект мультимножества, возвращаемый по значению. Ниже приведен пример синтаксиса.
bagresult = bag.create_union(bag2);
В этом примере новый объект мультимножества, созданный с помощью функции create_union, возвращается по значению, а затем это значение возврата присваивается переменной bagresult посредством оператора присваивания, определенного для класса d_Bag<T>.
friend d_Bag<T>
operator+(const d_Bag<T> &Ы, const d_Bag<T> &b2)
Создается объединение аргументов функции bl и Ь2, а результирующее мультимножество возвращается по значению. Ниже приведен пример синтаксиса.
bagresult = bagl + bag2
В этом примере значение, возвращаемое функцией operator+, присваивается переменной bagresult.
d_Bag<T> &
intersection_of(const d_Bag<T> &bl, const d_Bag<T> &b2)
Создается пересечение аргументов функции bl и Ь2, которое размещается в объекте результата. Исходные элементы конечного объекта мультимножества удаляются до начала операции, и для каждого из них вызывается деструктор (если таковой определен для данного типа). Функция возвращает ссылку на этот объект. Ниже приведен пример синтаксиса.
bag.intersection_of(bagl, bag2);
d_Bag<T> &
intersection_with(const d_Bag<T> &b2)
Создается пересечение из элементов данного объекта мультимножества и мультимножества Ь2. Результат пересечения помещается в исходный объект. Функция возвращает ссылку на этот объект. Ниже приведен пример синтаксиса.
- bag.intersection_with(bag2);
d_Bag<T> &
operator*=(const d_Bag<T> &b2)
Эта функция выполняет ту же операцию, что и предыдущая функция intersection_with. Вычисляется пересечение данного объекта и мультимножества Ь2, а результат размещается в исходном объекте. Функция возвращает ссылку на этот объект. Ниже приведен пример синтаксиса.
bag *= bag2;
d_Bag<T>
create_intersection(const d_Bag<T> &b2) const
Создается пересечение из элементов данного объекта мультимножества и мультимножества Ь2. Исходный объект при этом не изменяется, а результат представляет собой новый объект мультимножества, возвращаемый по значению. Ниже приведен пример синтаксиса.
bagresult = bag.create_intersection(bag2);
В этом примере новый объект мультимножества, созданный с помощью функции create_intersection, возвращается по значению, а затем присваивается переменной bagresult посредством оператора присваивания, определенного для класса d_Bag<T>.
friend d_Bag<T>
operator*(const d_Bag<T> &Ы, const d_Bag<T> &b2)
Создается пересечение аргументов функции bl и Ь2; а результатом является мультимножество, возвращаемое по значению. Ниже приведен пример синтаксиса.
bagresult = bagl * bag2
В этом примере значение, возвращаемое функцией operator*, присваивается переменной bagresult.
d_Bag<T> &
difference_of(const d_Bag<T> &bl, const d_Bag<T> &b2)
Создается разность аргументов функции Ы и Ь2 (Ы - Ь2), которая размещается в объекте результата. Исходные элементы этого объекта мультимножества удаляются до начала операции, и для каждого из них вызывается деструктор (если таковой определен для данного типа т). Функция возвращает ссылку на этот объект. Ниже приведен пример синтаксиса.
bag.difference_of(bagl, bag2);
В этом примере возвращаемое значение вызова функции difference_of является ссылкой на мультимножество bag.
d_Bag<T> &'
difference_with(const d_Bag<T> &b2)
Выполняется вычитание мультимножества Ь2 из данного объекта мультимножества. Результат помещается в исходный объект. Функция возвращает ссылку на этот объект. Ниже приведен пример синтаксиса.
bag.difference_with(bag2) ;
d_Bag<T> &
operator-=(const d_Bag<T> &b2)
Эта функция выполняет ту же операцию, что и предыдущая функция difference_with. Вычисляется разность данного объекта и мультимножества Ь2, а результат размещается в исходном объекте. Функция возвращает ссылку на этот объект. Ниже приведен пример синтаксиса.
bag -= bag2;
d_Bag<T>
create_difference(const d_Bag<T> &b2) const
Создается разность данного объекта мультимножества и мультимножества Ь2 (изданного мультимножества вычитается мультимножество Ь2). Исходный объект при этом не изменяется, а результат представляет собой новый объект мультимножества, возвращаемый по значению. Ниже приведен пример синтаксиса.
bagresult = bag.create_difference(bag2);
friend d_Bag<T>
operator-(const d_Bag<T> &bl, const d_Bag<T> &b2)
Вычисляется разность аргументов функции Ы и Ь2; результатом является мультимножество, возвращаемое по значению. Ниже приведен пример синтаксиса, bagresult = bagl - bag2
d_Collection<T> — абстрактный базовый класс для каждого класса коллекции ODMG. Создание экземпляров этого класса невозможно. Класс d_Collection<T> выведен из класса d_Object, поэтому экземпляры любых производных классов коллекций являются потенциально перманентными. Некоторые функции-члены могут быть чистыми виртуальными функциями, причем каждая реализация “сама" вправе решать, какие функции будут обычными виртуальными, а какие — чистыми.
class Т { public:class Т { public:Т & d_BooleanТ & d_BooleanЭлемент типа т должен поддерживать следующие операции.
class Т { public:
class Т { public:
Т & d_Boolean
Т & d_Boolean
то ;
Т(const TS) ;
~Т () ;
operator”(const Т s);
operator””(const Ts, const TS);
Другими словами, должна быть обеспечена возможность инициализации типа Т как без значения, так и посредством другого значения того же типа. Кроме того, должна быть представлена возможность присвоения экземпляру типа Т другого экземпляра типа т, а также возможность сравнения двух экземпляров типа Т на равенство. Наконец, должна быть гарантирована возможность разрушения любого экземпляра типа т.
Синопсис
template <class Т> class public:
d_Collection : public d_Object {
virtual
~d_Collection();
d_Collection<T> s
assign_from(const d_Collection<T> Sc2); '
friend d_Boolean
operator””(const d_Collection<T> Scl, const d_Collection<T> Sc2);
friend d_Boolean
operator! = (const d_Collection<T> Scl,
const d_Collection<T> Sc2);
unsigned long
cardinality() const;
d_Boolean
is_empty() const;
d_Boolean
is_ordered() const;
d_Boolean
allows_duplicates() const;
d_Boolean
contains_element(const T sval) const;
void
insert_element(const T Sval);
void
remove_element(const T &val);
void
remove_all();
void
remove_all(const T sval);
d_Iterator<T>
create_iterator() const;
d_Iterator<T>
begin() const;
d_Iterator<T>
end() const;
T
select_element(const char *OQL_pred) const
d_Iterator<T>
select(const char *OQL_pred) const;
int
query(d_Collection<T> sc,
-const char *OQL_pred) const;
d_Boolean
exists_element(const char *OQL_pred) const
protected:
d_Collection(const d_Collection<T> Sc2);
d_Collection<T> s
operator”(const d_Collection<T> sc2);
d_Collection();
Онисание
~d_Collection()
Виртуальный деструктор. Все элементы коллекции удаляются. Если элемент типа Т имеет деструктор, он вызывается для каждого элемента.
d_Collection<T> &
assign_from(const d_Collection<T> &c2)
Эта функция присваивает данному объекту элементы коллекции с2. Элементы, которые содержатся в данной коллекции до начала операции, удаляются. После выполнения операции объект содержит те же элементы, которые входят в состав коллекции с2. Функция возвращает ссылку на данный объект.
Каждый тип коллекции, выведенной из класса d_Collection<T> (например, d_Set<T> и d_List<T>), обеспечивает оператор присваивания, позволяющий присвоить один экземпляр некоторого типа коллекции другому экземпляру такого же типа коллекции (setl = set2). Важно то, что функцию assign_from можно использовать для операции присваивания с участием различных типов коллекций, например d_Set<T> и d_List<T>.
friend d_Boolean
operator==(const d_Collection<T> &cl, const d_Collection<T> &c2)
Оператор равенства возвращает истинное значение, если коллекции cl и с2 равны; в противном случае возвращается ложное значение.
friend d_Boolean
operator!=(const d_Collection<T> &cl, const d_Collection<T> &c2)
Оператор неравенства возвращает истинное значение, если коллекции cl и с2 не равны; в противном случае возвращается ложное значение.
unsigned long
cardinality() const
Эта функция возвращает количество элементов в коллекции.
d_Boolean
is_empty() const
Если данная коллекция пуста, функция возвращает истинное значение, в противном случае — ложное.
d_Boolean
is_ordered() const
Если данная коллекция упорядочена (d_List<T> и d_Varray<T>), эта функция возвращает истинное значение, в противном случае — ложное.
d_Boolean
allows_duplicates() const
Если данная коллекция позволяет наличие повторяющихся элементов (что характерно для классов d_Bag<T>, d_List<T> и d_Varray<T>), эта функция возвращает истинное значение, в противном случае — ложное.
d_Boolean
contains_element(const Т &val) const
Эта функция возвращает истинное значение, если данная коллекция содержит элемент со значением val; в противном случае она возвращает ложное значение.
void
insert_element(const T &val)
Эта функция вставляет в коллекцию новый элемент со значением val. Если коллекция представляет собой множество (класс d_Set<T>), это множество не изменится, если оно уже содержит элемент со значением val. Если коллекция является списком (класс d_List<T>), новый элемент будет размещен в конце этого списка. Если коллекция является массивом (класс d_Varray<T>), размер этого массива увеличится на 1, а новый элемент будет размещен в специально созданной для него последней позиции.
void.
remove_element(const Т &val)
Эта функция удаляет из коллекции один элемент, равный значению val. В упорядоченных коллекциях удаляется элемент, который встретился первым при выполнении прямой (однонаправленной) итерации коллекции.
void
remove_all()
Эта функция удаляет из коллекции все элементы. После завершения операции коллекция становится пустой.
void
remove_all(const Т &val)
Из коллекции удаляются все элементы, равные значению val. Эта функция добавлена в версии ODMG 2.0.
d_Iterator<T>
create_iterator() const
Функция возвращает итератор (экземпляр класса d_lterator<T>), расположенный в позиции первого элемента коллекции.
d_Iterator<T> begin () const
Функция возвращает итератор (экземпляр класса d_iterator<T>), расположенный в позиции первого элемента коллекции. Эта функция нужна для STL- совместимости при использовании итераторов. Она возвращает то же значение, что и функция create_iterator.
d_Iterator<T> end() const
Функция возвращает итератор (экземпляр класса d_lterator<T>), расположенный сразу за последним элементом коллекции. Эта функция нужна для STL-совместимости при использовании итераторов.
т
select_element(const char *OQL_pred) const
Эта функция применяет OQL-предикат к элементам коллекции и возвращает значение первого элемента, для которого данный предикат истинен. В версии ODMG-93 1.2 эта функция имела тип возврата const т &, который в версии 2.0 был заменен типом т.
d_Iterator<T>
select(const char *OQL_pred) const
Эта функция возвращает итератор для всех (по очереди) элементов коллекции, для которых данный предикат принимает истинное значение.
int
query(d_Collection<T> &c, const char *OQL_pred) const
Эта функция помещает в возвращаемую коллекцию с все элементы данной коллекции, для которых OQL-предикат принимает истинное значение. Если значение возврата функции оказывается ненулевым, это означает возникновение ошибки. Конкретные значения ошибок еще не стандартизированы.
d_Boolean
exists_element(const char *OQL_pred) const
Эта булева функция выполняет операцию навешивания квантора существования. Она возвращает истинное значение, если существует хотя бы один элемент, для которого OQL-предикат истинен. В противном случае она возвращает ложное значение.
d_Collection(const d_Collection<T> &с2)
Конструктор копии доступен только для конкретных производных классов.
d_Collection<T> &
operator=(const d_Collection<T> &c2)
Защищенный (protected) оператор присваивания доступен только для конкретных производных классов.
d_Collection ()
Защищенный (protected) нулевой конструктор доступен только для конкретных производных классов. Создание экземпляров этого абстрактного класса невозможно.
Имя: d Database
Класс, представляющий интерфейс с базой данных.
Синопсис
class d_Database {
public:
static d_Database * const transient_memory;
enum access_status {not_open, read_write, read_only, exclusive}; d_Database();
voidopen(const char *db_name,
access_status access=read_write);
voidclose();
voidset_object_name(const d_Ref_Anysobjref,
const char ‘name);
voidrename_object(const char *oldName,
const char ‘newName);
d_Ref_Anylookup_object(const char ‘name) const;
private:
d_Database(const d_Database s);
d_Database &operator=(const d_Database S);
Описание
static d_Database ‘const transient_memory
Этот указатель применяется вместе с оператором new класса d_Object для обозначения того, что объект должен быть размещен во временной (транзитной) памяти. Он позволяет использовать единый интерфейс с оператором new для создания как перманентных, так и транзитных объектов. Никакой реальной “базы данных”, связанной с транзитной памятью, не существует. В версии 1.2 стандарта ODMG-93 эта переменная объявлена следующим образом.
static const d_Database ‘const transient_memory
Тип указателя был изменен в версии 2.0.
enum access_status;
Это перечисление определяет возможные уровни доступа к открытой базе данных.
d_Database()
Нулевой конструктор инициализирует объект базы данных.
void
open(const char *db_name, access_status access)
Эта функция используется для открытия базы данных с именем db name посредством уровня доступа access. Данная функция находит указанную базу данных и устанавливает с ней соединение. Любая база данных должна быть открыта, прежде чем можно будет получить доступ к ее объектам. При попытке открыть уже открытую базу данных возбуждается исключение d_Error_DatabaseOpen.
void close ()
Эта функция закрывает соединение с базой данных. При попытке получить доступ к объекту базы данных после ее закрытия возбуждается исключение d_Error_DatabaseClosed.
void
set_object_name(const d_Ref_Any &objref, const char ‘name)
Эта функция устанавливает соответствие между данным объектом и именем. В версии 1.2 стандарта ODMG-93 объект мог иметь только одно имя. Согласно версии 2.0 ODMG, объект может иметь несколько имен. В версии 1.2, если объект уже имеет имя, после выполнения этой функции оно заменяется новым, а в версии 2.0 с объектом связывается дополнительное имя. Если заданное имя не уникально в базе данных, генерируется исключение d_Error_NameNotUnique. В версии 1.2 стандарта ODMG-93 при удалении объекта автоматически удаляется и его имя. В версии ODMG 2.0 имена автоматически не удаляются. Все имена, связанные с некоторым объектом, можно удалить, вызвав эту функцию и передав в качестве второго аргумента значение 0.
void
rename_object(const char *oldName, const char ‘newName)
Эта функция заменяет старое имя объекта oldName новым newName. Если новое имя не уникально, возбуждается исключение d_Error_NameNotUnique, и в результате остается старое имя объекта. Имя объекта можно удалить, задав в качестве аргумента newName значение 0.
d_Ref_Any
lookup_object(const char ‘name) const
Эта функция делает попытку найти объект с заданным именем и возвращает ссылку на найденный объект. Если объект с данным именем не существует, возвращаемая ссылка имеет null-значение.
d_Database(const d_Database &)
d_Database &
operator=(const d_Database &)
Это закрытые объявления. Их нельзя выполнить ни с каким объектом класса d Database.
class d_Date { public:enum Weekday {class d_Date { public:enum Weekday {enum Month {enum Month {d_Date & d_Date & unsigned short unsigned short unsigned short unsigned short Weekday Month d_Boolean static d_Boolean static d_Dated_Date & d_Date & d_Date & d_Date & d_Date & d_Date d_Date & d_Date & d_Date & d_Date friend d_Datefriend d_Date friend d_Intervalfriend d_Datefriend d_Boolean friend d_Boolean friend d_Boolean friend(d_Boolean friend d_Boolean friend d_Booleand_Boolean const;d_Date & d_Date & unsigned short unsigned short unsigned short unsigned short Weekday Month d_Boolean static d_Boolean static d_Dated_Date & d_Date & d_Date & d_Date & d_Date & d_Date d_Date & d_Date & d_Date & d_Date friend d_Datefriend d_Date friend d_Intervalfriend d_Datefriend d_Boolean friend d_Boolean friend d_Boolean friend(d_Boolean friend d_Boolean friend d_Booleand_Boolean const;
class d_Date { public:
enum Weekday {
class d_Date { public:
enum Weekday {
enum Month {
enum Month {
d_Date & d_Date & unsigned short unsigned short unsigned short unsigned short Weekday Month d_Boolean static d_Boolean static d_Date
d_Date & d_Date & d_Date & d_Date & d_Date & d_Date d_Date & d_Date & d_Date & d_Date friend d_Date
friend d_Date friend d_Interval
friend d_Date
friend d_Boolean friend d_Boolean friend d_Boolean friend(d_Boolean friend d_Boolean friend d_Boolean
d_Boolean const;
d_Date & d_Date & unsigned short unsigned short unsigned short unsigned short Weekday Month d_Boolean static d_Boolean static d_Date
d_Date & d_Date & d_Date & d_Date & d_Date & d_Date d_Date & d_Date & d_Date & d_Date friend d_Date
friend d_Date friend d_Interval
friend d_Date
friend d_Boolean friend d_Boolean friend d_Boolean friend(d_Boolean friend d_Boolean friend d_Boolean
d_Boolean const;
Класс, представляющий дату, состоящую из года, месяца и числа.
Синопсис
Sunday = 0, Monday = 1, Tuesday = 2,
Wednesday = 3, Thursday = 4, Friday = 5, Saturday = 6 } ;
January = 1, February = 2, March = 3,
April = 4, May = 5, June = 6, July = 7, August = 8, September = 9, October = 10, November = 11, December = 12 } ;
d_Date(); // установка текущей даты d_Date(unsigned short year, unsigned short day_of_year);
d_Date(unsigned short year,
unsigned short month, unsigned short day);
d_Date(const d_Date Sd2);
d_Date(const d_Timestamp Sts);
operator=(const d_Date Sd2);
operator=(const d_Timestamp Sts);
year() const;
month() const;
day() const;
day_of_year() const;
day_of_week() const;
month_of_year() const ;
is__leap_year () const;
is_leap_year(unsigned short year);
current();
next(Weekday wd) ;
previous(Weekday wd);
operator+=(const d_Interval si);
operator+=(int ndays);
operator++();
operator++(int);
operator-=(const d_Interval si);
operator—(int ndays);
operator—();
operator—(int);
operators- (const d_Date sd,
const d_Interval si);
operators- (const d_Interval si, const d_Date sd);
operator- (const d_Date sdl, const d_Date Sd2);
operator- (const d_Date sd,
const d_Interval si);
operator==(const d_Date sdl, const d_Date Sd2) operator!=(const d_Date sdl, const d_Date Sd2) operator< (const d_Date sdl, const d_Date &d2) operator<=(const d_Date sdl, const d_Date Sd2) operator> (const d_Date sdl,.const.d^Date Sd2) operator>=(const d_Date Sdl, const d_bate Sd2) is_between(const d_Date Sd2, const d_Date Sd3)
friend d_Boolean
overlaps(const d_Date ssL, const d_Date &eL, const d_Date &sR, const d_Date seR);
friend d_Boolean
overlaps(const d_Timestamp &sL, const d_Timestamp &eL, const d_Date &sR, const d_Date seR);
friend d_Boolean
overlaps(const d_Date &sL, const d_Date &eL, const d_Timestamp &sR, const d_Timestamp &eR) ;
static int int
static int
days_in_year(unsigned short year);
days_in_year() const;
days_in_month(unsigned short year, unsigned short month) ;
int
static d_Boolean
days_in_month() const; is_valid_date(unsigned short year, unsigned short month,
unsigned short day);
Описание
enum Weekday
Это перечисление представляет дни недели.
enum Month
Это перечисление представляет месяцы года.
d_Date()
Нулевой конструктор инициализирует объект текущей датой.
d_Date(unsigned short year, unsigned short day_of_year)
Этот конструктор инициализирует объект заданным годом и днем года. Для аргумента year должно быть задано полное четырехзначное значение года (например, 2001). Аргумент day_of_year должен иметь значение в диапазоне 1—366 (для високосного года).
d_Date(unsigned short year, unsigned short month, unsigned short day);
Этот конструктор инициализирует объект на основе заданного года, месяца и дня. Для аргумента year должно быть задано полное четырехзначное значение года (например, 2001). Значение месяца должно находиться в диапазоне 1—12, а значение дня — в диапазоне 1—31.
d_Date(const d_Date &d2)
Конструктор копии инициализирует объект датой, которая задана с помощью аргумента d2.
d_Date(const d_Timestamp Sts)
Этот конструктор инициализирует объект с помощью компонентов года, месяца и числа, заданных в аргументе ts.
d_Date &
operator=(const d_Date &d2)
Оператор присваивает объекту значение даты аргумента d2 и возвращает ссылку на этот объект.
d_Date &
operator=(const d_Timestamp Sts)
Оператор присваивает объекту компоненты года, месяца и числа, заданные в аргументе ts, возвращая ссылку на этот объект.
unsigned short year() const
Функция возвращает четырехзначное значение года (например, 2001).
unsigned short month() const
Функция возвращает целочисленное значение месяца (1 — 12). Целочисленное значение каждого месяца определено перечислением Month.
unsigned short day() const
Функция возвращает целочисленное значение дня в пределах месяца.
unsigned short
day_of_year() const
Функция возвращает целочисленное значение дня в пределах года.
Weekday
day_of_week() const
Функция возвращает значение перечисления Weekday, представляющее день недели.
Month
month of year() const;
Функция возвращает значение перечисления Month, представляющее месяц года.
d_Boolean
is_leap_year() const
Эта булева функция возвращает истинное значение, если данная дата принадлежит високосному году; в противном случае она возвращает ложное значение.
static d_Boolean
is_leap_year(unsigned short year)
Эта статическая булева функция-член возвращает истинное значение, если данный год является високосным; в противном случае она возвращает ложное значение.
static d_Date current()
Эта статическая функция возвращает значение объекта класса d_Date, содержащего текущую дату.
d_Date &
next(Weekday wd)
Эта функция изменяет дату так, чтобы она соответствовала ближайшей будущей дате, в которой используется день недели, заданный аргументом wd. Функция возвращает ссылку на обновленный объект. Например, каждый последующий вызов функции next с аргументом Date: .-Monday изменит значение даты, чтобы оно соответствовало следующему понедельнику.
d_Date &
previous(Weekday wd)
Эта функция изменяет дату так, чтобы она соответствовала ближайшей прошлой дате, в которой используется день недели, заданный аргументом wd. Функция
возвращает ссылку на обновленный объект. Например, с помощью функции previous можно легко получить значение даты для прошлого вторника.
d_Date &
operator+=(const d_Interval &i)
Эта функция добавляет к значению даты количество дней, заданное аргументом i (i.dayO), и возвращает ссылку на обновленный объект даты. Положительное значение, возвращаемое выражением i.day(), передвигает дату вперед, а отрицательное — назад.
d_Date &
operator+=(int ndays)
Эта функция добавляет к значению даты количество дней, заданное аргументом ndays, и возвращает ссылку7 на обновленный объект даты. Положительное значение аргумента ndays передвигает дату вперед, а отрицательное — назад.
d_Date & operator** ()
Этот префиксный оператор инкрементирования увеличивает значение даты на один день и возвращает ссылку на обновленный объект даты.
d_Date
operator**(int)
Этот постфиксный оператор инкрементирования увеличивает значение даты на один день и возвращает исходный объект класса d_Date по значению.
d_Date &
operator-=(const d_Interval &i)
Эта функция вычитает из значения даты количество дней, заданное аргументом i (i . day ()), и возвращает ссылку на обновленный объект даты.
d_Date &
operator-=(int ndays)
Эта функция вычитает из значения даты количество дней, заданное аргументом ndays, и возвращает ссылку на обновленный объект даты. Положительное значение аргумента ndays передвигает дату назад, а отрицательное — вперед.
d_Date &
operator—()
Этот префиксный оператор декрементирования переводит значение даты на один день назад и возвращает ссылку на обновленный объект даты.
d_Date
operator--(int)
Этот постфиксный оператор декрементирования переводит значение даты на один день назад и возвращает исходное значение даты.
friend d_Date
operator*(const d_Date &d, const d_Interval &i)
friend d_Date
operator*(const d_Interval &i, const d_Date &d)
Эти операторы добавляют количество дней, заданное аргументом i (i.dayO), к дате, заданной аргументом d, и возвращают новый объект типа d_Date по значению.
friend d_Interval
operator-(const d_Date &dl, const d_Date &d2)
Эта функция возвращает интервал времени между датами dl и d2, т.е. значение выражения (dl - d2). Если дата dl стоит перед датой d2, результат будет отрицательным. Эта функция добавлена в версии 2.0 стандарта ODMG.
friend d_Date
operator-(const d_Date &d, const d_Interval &i)
Этот оператор вычитает количество дней, заданное аргументом i (i.dayO), из даты, заданной аргументом d, и возвращает новое значение даты.
friend d_Boolean
operator==(const d_Date &dl, const d_Date &d2)
Булев оператор равенства возвращает истинное значение, если два объекта класса d_Date представляют одинаковую дату; в противном случае он возвращает ложное значение.
friend d_Boolean
operator!=(const d_Date &dl, const d_Date &d2)
Этот булев оператор неравенства возвращает истинное значение, если два объекта класса d Date представляют разные даты; в противном случае он возвращает ложное значение.
friend d_Boolean
operator<(const d_Date &dl, const d_Date &d2)
Этот булев оператор “меньше” возвращает истинное значение, если дата dl стоит перед датой d2; в противном случае он возвращает ложное значение.
friend d_Boolean
operator<=(const d_Date &dl, const d_Date &d2)
Этот булев оператор “меньше или равно” возвращает истинное значение, если дата dl стоит перед датой d2 или совпадает с ней; в противном случае он возвращает ложное значение.
friend d_Boolean
operator>(const d_Date &dl, const d_Date &d2)
Этот булев оператор “больше” возвращает истинное значение, если дата dl стоит после даты d2; в противном случае он возвращает ложное значение.
friend d_Boolean
operator>=(const d_Date &dl, const d_Date &d2)
Этот булев оператор “больше или равно” возвращает истинное значение, если дата dl стоит после даты d2 или совпадает с ней; в противном случае он возвращает ложное значение.
d_Boolean
is_between(const d_Date Sdl, const d_Date &d2) const
Эта булева функция возвращает истинное значение, если значение даты объекта лежит в интервале между датами dl и d2; в противном случае она возвращает ложное значение. Эта функция возвращает истинное значение и в том случае, если значение объекта равно либо dl, либо d2.
Если dl <= d2, то выражение
d. is_between(dl, d2)
эквивалентно выражению
(d >= dl) && (d <= d2).
Если dl >= d2, то выражение
d.is between(dl, d2)
эквивалентно выражению
(d >= d2) && (d <= dl).
friend d_Boolean
overlaps(const d_Date &sL, const d_Date &eL, const d_Date &sR, const d_Date &eR)
Эта булева дружественная функция возвращает истинное значение, если промежуток времени L (ограниченный датами sL и eL) включает любой общий период времени с промежутком R (ограниченным датами sR и eR). В противном случае она возвращает ложное значение.
friend d_Boolean
overlaps(const d_Timestamp &sL, const d_Timestamp &eL, const d_Date &sR, const d_Date &eR).
Эта булева дружественная функция возвращает истинное значение, если промежуток времени L (ограниченный датами sL и eL) включает любой общий период времени с промежутком R (ограниченным датами sR и eR). В противном случае она возвращает ложное значение.
friend d_Boolean■
overlaps(const d_Date &sL, const d_Date SeL,
const d_Timestamp &sR, const d_Timestamp &eR)
Эта булева дружественная функция возвращает истинное значение, если промежуток времени L (ограниченный датами sL и eL) включает любой общий период времени с промежутком R (ограниченным датами sR и eR). В противном случае она возвращает ложное значение.
static int
days_in_year(unsigned short year)
Эта статическая функция возвращает количество дней в году, заданном аргументом year.
int
days_in_year() const
Эта функция-член возвращает количество дней в году на основе значения года в данном объекте.
static int
days_in_month(unsigned short year, unsigned short month)
Эта статическая функция-член возвращает количество дней месяца, заданного аргументом month, для года, заданного аргументом year.
int
days_in_month() const
Эта функция-член возвращает количество дней в месяце на основе текущих значений месяца и года в данном объекте.
static d_Boolean
is_valid_date(unsigned short year, unsigned short month, unsigned short day)
Эта функция возвращает истинное значение, если заданные значения года, месяца и дня представляют действительное значение даты. В противном случае она возвращает ложное значение."'
Имя: d_Dictionarg<K,V>
Неупорядоченная коллекция (словарь) пар (ассоциаций), состоящих из ключа и значения. Ключ и значение могут иметь произвольные типы. При этом только одна пара может иметь некоторое конкретное значение ключа. Класс d_Dictionary выведен из класса d_Collection, в котором типом элемента является класс d_Association. Унаследованные от класса d_Collection операции, предназначенные для элементов коллекции, выполняются над экземплярами класса d_Association. Итераторы, возвращаемые функциями, которые унаследованы от класса d_Collection, возвращают экземпляры класса d_Association.
Если при вызове унаследованной функции insert_element словарь уже содержит экземпляр класса d_Association с ключевым значением, переданным функции, то этот экземпляр заменяется, т.е. с заданным ключом связывается новое значение. Если заданный ключ в словаре не обнаружен, в словарь добавляется новый экземпляр класса d_Association.
При вызове функции remove_element ключ и значение параметра типа d_Ass ociation должны совпадать с ключом и значением элемента, подлежащего удалению. Если словарь не содержит такой пары, возбуждается исключение d_Error_ElementNotFound. Аналогично функция contains_element возвращает истинное значение только в том случае, если словарь содержит такой экземпляр класса d_Association, компоненты которого (ключ и значение) совпадают с компонентами экземпляра класса d_Association, переданного в качестве параметра.
Класс d_Dictionary — это новая коллекция, добавленная в стандарт ODMG 2.0.
Синопсис
template <class К, class V> class d_Association {
public:
Кkey;
value;
d_Association(const К sk, const V sv) : key(k) , value(v) { )
} ;
template <class K, class V>
class d_Dictionary : public d_Collection<d_Association<K,V> > { public:
d_Dictionary ();
d_Dictionary(const d_Dictionary<K,V> Sdict); ~d_Dictionary();
d_Dictionary<K,V> &operator=(const d_Dictionary<K,V> edict);
voidbindfeonst К sk, const V Sv);
voidunbind(const К sk);
lookup(const К sk) const;
d_Booleancontains_key(const К sk) const;
};
Оппсанпе
d_Dictionary()
Нулевой конструктор инициализирует экземпляр класса d_Dictionary без элементов.
d_Dictionary(const d_Dictionary<K,V> &dict)
Конструктор копии инициализирует словарь таким образом, чтобы он содержал копию тех пар, которые входят в состав словаря diet.
~d_Dictionary()
Деструктор удаляет словарь, т.е. удаляет все ассоциации, которые он содержит.
d_Dictionary<K,V> &
operator=(const d_Dictionary<K,V> Sdict)
Оператор присваивания удаляет текущие элементы словаря, заменяя их копией ассоциаций, содержащихся в словаре diet.
void
bind(const К &k, const V &v)
Эта функция устанавливает в словаре ассоциацию ключа к и значения v. Эта ассоциация добавляется в словарь, если в нем еще не существует ассоциации, включающей ключ со значением к. Если же словарь уже содержит экземпляр класса d_Association с ключом к, то связанное с ним значение заменяется значением v.
void
unbind(const К &к)
Если словарь содержит экземпляр класса d_Association с ключом к, этот экземпляр удаляется из словаря.
V
lookup(const К &k) const
Делается попытка получить доступ к элементу словаря типа d_Association с ключом, равным к. Если словарь не содержит такого элемента, возбуждается исключение d_Error_ElementNotFound.
d_Boolean
contains_key(const К &k) const
Эта функция возвращает истинное значение, если словарь содержит ключ, равный к.
Имя: il Error
Класс d Error используется для представления исключений, предусмотренных в стандарте ODMG. Он выведен из С++-класса exception, определенного в стандарте ANSI/ISO C++.
Спнопсис
class d_Error : public exception { public:
typedef d_Long kind;
d Error();
d Error(const d Error 6e2)
d_Error(kind the_kind);
~d_Error();
kind
get_kind();
void
set_kind(kind the_kind) ;
const char
what () const throw();
d_Error
&
operator« (d_Char d) ;
d_Error
&
operator<<(d_Short s) ;
d_Error
&
operator<<(d_UShort us);
d_Error
&
operator<<(d_Long L);
d_Error
&
operator<<(d_ULong UL);
d_Error
&
operator<<(d_Float f);
d_Error
&
operator<<(d_Double d);
d_Error
&
operator<<(const char *s);
d_Error
&
operator<<(const d_String Ss)
d Error };
&
operator<<(const d_Error se2)
Описание
typedef d_Long kind
Тип kind представляет идентификатор, означающий конкретную ошибку, которая произошла при выполнении той или иной операции. Список возможных значений для типа kind приведен в разделе “Виды ошибок”.
d_Error()
Нулевой конструктор инициализирует объект ошибки, а ее вид (kind) устанавливается равным значению d_Error_None.
d_Error(const d_Error &e2)
Конструктор копии инициализирует объект ошибки, используя значение е2.
d_Error(kind the_kind)
Этот конструктор инициализирует объект ошибки значением the_kind.
~d_Error()
Деструктор удаляет объект, освобождая строку ошибки, если таковая используется.
kind
get_kind() -
Эта функция возвращает (считывает) вид ошибки.
void
set_kind(kind the_kind)
Эта функция устанавливает (записывает) вид ошибки.
const char *
what() const throw ()
Эта виртуальная функция унаследована от базового С++-класса exception и используется для доступа к строке, которая описывает зафиксированную ошибку. За освобождение строки, возвращаемой функцией what, отвечает объект класса d_Error.
d_Error &
operator<<(d_Char с)
Эта функция добавляет символ ASCII в конец строки ошибки, которая возвращается функцией what.
d_Error &
operator<<(d_Short s)
Эта функция добавляет ASCII-представление короткого целого в конец строки ошибки, которая возвращается функцией what.
d_Error &
operator<<(d_UShort us)
Эта функция добавляет ASCII-представление короткого целого без знака в конец строки ошибки, которая возвращается функцией what.
d_Error &
operator<<(d_Long L)
Эта функция добавляет ASCII-представление длинного целого в конец строки ошибки, которая возвращается функцией what.
d_Error &
operator<<(d_ULong UL)
Эта функция добавляет ASCII-представление длинного целого без знака в конец строки ошибки, которая возвращается функцией what.
d_Error &
operator<<(d_Float f)
Эта функция добавляет ASCII-представление числа с плавающей запятой в конец строки ошибки, которая возвращается функцией what.
d_Error &
operator<<(d_Double d)
Эта функция добавляет ASCII-представление числа с плавающей запятой двойной точности в конец строки ошибки, которая возвращается функцией what.
d_Error &
operator<<(const char *s)
Эта функция добавляет символьную строку в конец строки ошибки, которая возвращается функцией what.-
d_Error &■
operator<<(const d_String &s)
Эта функция добавляет экземпляр класса d_String в конец строки ошибки, которая возвращается функцией what.
d_Error &
operator<<(const d_Error &e2)
Эта функция добавляет содержимое другого экземпляра класса d Error, е2, в конец строки ошибки, которая возвращается функцией what.
Виды ошибок
d_Error_None
Не возникло никаких ошибок.
d_Error_DatabaseClassMismatch
Определение класса в приложении не совпадает с определением класса в базе данных.
d_Error_DatabaseClassUndefined
В схеме базы данных нет информации о данном классе.
d_Error_DatabaseClosed
Сделана попытка закрыть базу данных, которая уже закрыта (т.е. не открыта). Или была сделана попытка получить доступ к объекту закрытой базы данных.
d_Error_DatabaseOpen
Сделана попытка открыть базу данных, которая уже открыта.
d_Error_Date!nvalid
Сделана попытка установить объект типа d Date равным неверному значению даты.
d_Error_ElementNotFound
Сделана попытка получить доступ к элементу, которого нет в коллекции.
d_Error_IteratorDifferentCollections
Сделана попытка использовать итератор с коллекцией, с которой он не связан.
d_Error_IteratorExhausted
Была сделана попытка переместить итератор вперед, когда он уже достиг конца итерации.
d_Error_IteratorNotBackward
Сделана попытка переместить итератор назад в коллекции типа d_Set<T> или d_Bag<T>, которая не поддерживает обратной итерации.
d_Error_Member!sOfInvalidType
Второй аргумент шаблона (мт) в объявлении класса d_Rel_Ref<T, МТ>, d_Rel_Set<T,MT> или d_Rel_List<T,МТ> ссылается на данное-член типа Т, тип которого не совпадает с типом d_Rel_Ref<T,МТ>, d_Rel_Set<T,MT> или d_Rel_List<T, МТ>.
d_E г г о r_Membe г N о t Found
Второй аргумент шаблона (мт) в объявлении класса d_Rel_Ref<T,МТ>, d_Rel_Set<T,MT> или d_Rel_List<T,МТ> содержит строку, которая не соответствует имени ни одного из данных-членов в адресуемом классе т.
d_Error_NameNotUnique
Сделана попытка присвоить объекту имя, которое уже связано с некоторым объектом.
d_Error_PositionOutOfRange
В качестве индекса в коллекции использована позиция, которая не принадлежит диапазону действительных значений. Эта ошибка добавлена в версии 2.0 стандарта ODMG.
d_Error_QueгуParametercountInvalid
Количество аргументов, использованных для построения запроса с помощью объекта класса d_OQL_Query, не равно количеству аргументов, содержащихся в строке запроса.
d_Error_QueryParameterTypeInvalid
Неверен либо тип параметра запроса, либо тип значения возврата для запроса, типы компонентов которого известны базе данных.
d_Error_RefInvalid
Была сделана попытка разыменовать экземпляр класса d_Ref<T>, который ссылается на несуществующий объект (этот объект, возможно, удален).
d_Error_RefNull
Сделана попытка разыменовать нулевую ссылку.
d_Error_Time!nvalid
Сделана попытка установить объект класса d_Time равным неверному значению времени.
d_Error_TimestampInvalid
Сделана попытка установить объект класса d_Timestamp равным неверному значению даты и времени.
d_Erгor_TransaetionNotOpen
Обращение к функции commit, abort или checkpoint без первоначального вызова функции begin. Это исключение было добавлено в версии 2.0 стандарта ODMG.
d_Erгor_TransactionOpen
Функция d_Transaction: :begin была вызвана повторно для того же объекта без промежуточных обращений к функциям commit или abort.
d_Erгor_TypeInvalid
Сделана попытка инициализировать экземпляр класса d_Ref<T> ссылкой на объект, тип которого не совпадает с типом т или не является классом, открыто (т.е. с использованием спецификатора доступа public) выведенным из класса т.
Имя: il Extent<T>
Класс d_Extent<T> обеспечивает интерфейс с экстентом класса т. Экстент содержит все экземпляры класса Т, хранимые в базе данных. Он поддерживается автоматически по мере создания и удаления экземпляров класса Т. Это новый класс, введенный в версии 2.0 стандарта ODMG.
Синопспс
virtual
~d_Extent() ;
unsigned long d_Boolean d_Boolean d_Boolean d_Iterator<T> d_Iterator<T> d_Iterator<T> d_Ref<T>
cardinality() const;
is_empty() const;
allows_duplicates() const;
is_ordered() const;
create_iterator () const;
begin () const;
end() const;
select_element(const char *OQL_pred) const;
d_Iterator<T> int
d_Boolean
select(const char *OQL_pred) const;
query(d_Collection<d_Ref<T> > &c, const char *OQL_pred) const;
exists_element(const char *OQL_pred) const;
template <class Т> class d_Extent { public:
d_Extent(const d_Database *db, d_Boolean inc_subclasses =d_True);
protected:
d_Extent(const d_Extent<T> &e2);
d_Extent<T> &operator”(const d_Extent<T> &e2);
Описание
d_Extent(const d_Database *db, d_Boolean inc_subclasses = d_True)
Этот конструктор связывает объект с экстентом класса Т в базе данных db. Второй параметр означает, нужно ли включать в экстент подклассы класса Т.
~d_Extent()
Деструктор удаляет объект экстента. Реальный же экстент в базе данных не удаляется.
unsigned long
cardinality() const
Эта функция возвращает количество экземпляров класса Т в экстенте.
d_Boolean
is_empty() const
Эта функция возвращает истинное значение, если в экстенте нет ни одного экземпляра класса Т; в противном случае она возвращает ложное значение.
d_Boolean ,..:
allows_duplicates() const
Эта функция всегда возвращает ложное значение. В экстенте никогда не бывает повторяющихся элементов.
d_Boolean
is_ordered() const
Эта функция всегда возвращает ложное значение. Экземпляры класса Т не возвращаются в каком-то определенном порядке.
d_Iterator<T>
create_iterator() const
Эта функция возвращает итератор, используемый для опроса (итерирования) экземпляров класса т в экстенте.
d_Iterator<T> begin() const
Эта функция возвращает итератор, размещенный в экстенте на “начальном” экземпляре класса т. Заметьте, что элементам экстентов не свойственно быть упорядоченными каким-либо образом.
d_Iterator<T> end() const
Эта функция возвращает итератор, размещенный за конечным элементом экстента.
d_Ref<T>
select_element(const char *OQL_pred) const
Функция вычисляет булев OQL-предикат, заданный в параметре OQL pred, и возвращает ссылку на соответствующий экземпляр. Эта функция предназначена для предикатов запросов, которые имеют истинный результат только для единственного экземпляра класса т.
d_Iterator<T>
select(const char *OQL_pred) const
Функция вычисляет булев OQL-предикат, заданный в параметре OQL_pred, и возвращает итератор, используемый (при необходимости) для итерирования экземпляров класса т, для которых заданный предикат истинен.
int
query(d_Collection<d_Ref<T> > &с, const char *OQL_pred) const
Функция вычисляет булев OQL-предикат, заданный в параметре OQL_pred, и заполняет коллекцию с ссылками на экземпляры класса т, для которых предикат, заданный параметром OQL_pred, истинен.
d_Boolean
exists_element(const char * OQL_pred) const
Функция возвращает истинное значение, если в экстенте существует экземпляр класса т, для которого истинен булев OQL-предикат, заданный в параметре OQL_pred.
d_Extent(const d_Extent<T> &e2)
d_Extent<T> &
operator=(const d_Extent<T> &e2)
Невозможно сделать копию экстента или присвоить один экстент другому.
Имя: djnterval
Этот класс представляет промежуток времени. Он принимает ненормализованные входные данные и при доступе к ним возвращает уже нормализованные значения компонентов времени. Предположим, вы инициализируете объект значением, равным 130 минутам. При последующем вызове функций доступа этот объект возвратится в виде составного значения: 2 часа и 10 минут. Факт нормализации компонентов временных интервалов означает, что функция hour возвращает значение часов в диапазоне [—23,+23], а функции minute и second — значения минут и секунд соответственно в диапазоне [—59,+59]. Кроме того, целые значения компонентов будут либо нулевыми, либо отрицательными, либо положительными. Другими словами, компоненты времени не будут представлять собой “смесь” из положительных и отрицательных значений.
Синопсис
class d_Interval { public:class d_Interval { public:d_Interval & int int intfloat d_Booleand_Interval s d_Interval s d_Interval s d_Interval s d_Interval friend d_Intervald_Interval & int int intfloat d_Booleand_Interval s d_Interval s d_Interval s d_Interval s d_Interval friend d_Intervalfriend d_Interval friend d_Interval friend d_Interval friend d_Interval friend d_Booleanfriend d_Boolean friend d_Boolean friend d_Boolean friend d_Boolean friend d Booleanfriend d_Interval friend d_Interval friend d_Interval friend d_Interval friend d_Booleanfriend d_Boolean friend d_Boolean friend d_Boolean friend d_Boolean friend d Booleand_Interval(int days = 0, int hours = 0, int mins = 0, float secs = O.Of);
class d_Interval { public:
class d_Interval { public:
d_Interval & int int int
float d_Boolean
d_Interval s d_Interval s d_Interval s d_Interval s d_Interval friend d_Interval
d_Interval & int int int
float d_Boolean
d_Interval s d_Interval s d_Interval s d_Interval s d_Interval friend d_Interval
friend d_Interval friend d_Interval friend d_Interval friend d_Interval friend d_Boolean
friend d_Boolean friend d_Boolean friend d_Boolean friend d_Boolean friend d Boolean
friend d_Interval friend d_Interval friend d_Interval friend d_Interval friend d_Boolean
friend d_Boolean friend d_Boolean friend d_Boolean friend d_Boolean friend d Boolean
d_Interval(const d_Interval Si2);
operator=(const d_Interval &i2);
day() const;
hour() const;
minute!) const;
second () const;
is_zero() const;
operator+=(const d_Interval &i2);
operator-=(const d_Interval Si2);
operator*=(int i);
operator/=(int i);
operator-() const;
operatort(const d_Interval Sil, const d_Interval &i2);
operator-(const d_Interval sil, const d_Interval &i2);
operator*(const d_Interval sil, int i);
operator*(int i, const d_Interval si2); operator/(const d_Interval sil, int i); operator==(const d_Interval sil, const d_Interval &i2);
operator!=(const d_Interval sil, const d_Interval Si2);
operator<(const d_Interval sil, const d_Interval &i2);
operator<=(const d_Interval Sil, const d_Interval Si2);
operator>(const d_Interval Sil, const d_Interval Si2);
operator>=(const d_Interval Sil, const d Interval Si2);
Описание
d_Interval(int days=0, int hours=0, int mins=0, float secs=0.0f)
Конструктор инициализирует объект интервала заданными значениями суток, часов, минут и секунд.
d_Interval(const d_Interval &i2)
Конструктор копии инициализирует объект класса d lnterval значением, заданным параметром 12.
d_Interval &
operator=(const d_Interval &12)
Оператор присваивает объекту значение параметра 12.
int
day () const
Функция возвращает количество дней в данном интервале.
int
hour() const
Функция возвращает количество часов в данном интервале.
int
minute() const
Функция возвращает количество минут в данном интервале.
float
second() const
Функция возвращает количество секунд в данном интервале.
d_Boolean
is_zero() const
Если значения всех компонентов временного интервала равны нулю, эта функция возвращает истинное значение. Если хотя бы один из компонентов не равен нулю, функция возвращает ложное значение.
d_Interval &
operator+=(const d_Interval &i2)
К объекту интервала добавляется значение параметра i2, и возвращается ссылка на этот объект.
d_Interval &
operator-=(const d_Interval &i2)
Из объекта интервала вычитается значение параметра 12, и возвращается ссылка на этот объект.
d_Interval &
operator*=(int i)
Значение объекта интервала умножается на заданный множитель 1 целого типа, и возвращается ссылка на этот объект.
d_Interval &
operator/=(int i)
Значение объекта интервала делится на заданный делитель i целого типа, и возвращается ссылка на этот объект.
d_Interval
operator-() const:
Значение объекта интервала инвертируется, и результат возвращается по значению.
Имя: il lterator<T>
Этот класс используется для итерирования элементов коллекции. В классе d_Collection<T> определены функции create_iterator, begin и end, которые возвращают экземпляры класса d_Iterator<T>. Функции create_iterator и begin возвращают итераторы, расположенные в позиции первого элемента. Функция end возвращает значение итератора, которое логически соответствует расположению за последним элементом, и это значение используется для определения того, что достигнут конец итерации.
Синопсис
template <class Т> class public:template <class Т> class public:d_Iterator<T> S friend d_Booleand_Iterator<T> S friend d_Booleanfriend d_Booleanvoid d_Boolean voidd_Iterator<T> s d_Iterator<T> d_Iterator<T> s d_Iterator<T> TT void d_Booleanfriend d_Booleanvoid d_Boolean voidd_Iterator<T> s d_Iterator<T> d_Iterator<T> s d_Iterator<T> TT void d_Booleand_Iterator {d_Iterator(};d_Iterator(const d_Iterator<T> si2);~d_Iterator(};operator™(const d_Iterator<T> Si2);operator==(const d_Iterator<T> Sil, const d_Iterator<T> &i2};operator!=(const d_Iterator<T> Sil, const d_Iterator<T> Si2);reset();not_done() const;advance();operator++();operator++(int) ;operator-- ();operator—(int};get_element() const;operator*() const;replace_element(const T sval);next(T Sval);d_Iterator {d_Iterator(};d_Iterator(const d_Iterator<T> si2);~d_Iterator(};operator™(const d_Iterator<T> Si2);operator==(const d_Iterator<T> Sil, const d_Iterator<T> &i2};operator!=(const d_Iterator<T> Sil, const d_Iterator<T> Si2);reset();not_done() const;advance();operator++();operator++(int) ;operator-- ();operator—(int};get_element() const;operator*() const;replace_element(const T sval);next(T Sval);
template <class Т> class public:
template <class Т> class public:
d_Iterator<T> S friend d_Boolean
d_Iterator<T> S friend d_Boolean
friend d_Boolean
void d_Boolean void
d_Iterator<T> s d_Iterator<T> d_Iterator<T> s d_Iterator<T> T
T void d_Boolean
friend d_Boolean
void d_Boolean void
d_Iterator<T> s d_Iterator<T> d_Iterator<T> s d_Iterator<T> T
T void d_Boolean
d_Iterator {
d_Iterator(};
d_Iterator(const d_Iterator<T> si2);
~d_Iterator(};
operator™(const d_Iterator<T> Si2);
operator==(const d_Iterator<T> Sil, const d_Iterator<T> &i2};
operator!=(const d_Iterator<T> Sil, const d_Iterator<T> Si2);
reset();
not_done() const;
advance();
operator++();
operator++(int) ;
operator-- ();
operator—(int};
get_element() const;
operator*() const;
replace_element(const T sval);
next(T Sval);
d_Iterator {
d_Iterator(};
d_Iterator(const d_Iterator<T> si2);
~d_Iterator(};
operator™(const d_Iterator<T> Si2);
operator==(const d_Iterator<T> Sil, const d_Iterator<T> &i2};
operator!=(const d_Iterator<T> Sil, const d_Iterator<T> Si2);
reset();
not_done() const;
advance();
operator++();
operator++(int) ;
operator-- ();
operator—(int};
get_element() const;
operator*() const;
replace_element(const T sval);
next(T Sval);
Описание
d_Iterator()
Нулевой конструктор инициализирует объект итератора, который нельзя использовать для итерации, поскольку он еще не ссылается на конкретную коллекцию. Этому объекту нужно сначала присвоить итератор, связанный с некоторой коллекцией. Эта функция поддерживается для совместимости с итераторами библиотеки STL.
d_Iterator(const d_Iterator<T> &i2)
Конструктор копии инициализирует итератор значением, равным значению итератора, заданного параметром i2. Эта функция поддерживается для совместимости с итераторами STL.
~d_Iterator()
Деструктор класса.
d_Iterator<T> &
operator=(const d_Iterator<T> &i2)
Имя: d_lteratBr<T>
Этот класс используется для итерирования элементов коллекции. В классе d_Collection<T> определены функции create_iterator, begin и end, которые возвращают экземпляры класса d_lterator<T>. Функции create_iterator и begin возвращают итераторы, расположенные в позиции первого элемента. Функция end возвращает значение итератора, которое логически соответствует расположению за последним элементом, и это значение используется для определения того, что достигнут конец итерации.
Синопсис
template <class Т> classtemplate <class Т> classpublic:public:d_Iterator<T> & friend d_Booleand_Iterator<T> & friend d_Booleanfriend d_Booleanvoid d_Boolean void d_Iterator<T> & d_Iterator<T> d_Iterator<T> & d_Iterator<T> TT void d_Boolean };friend d_Booleanvoid d_Boolean void d_Iterator<T> & d_Iterator<T> d_Iterator<T> & d_Iterator<T> TT void d_Boolean };d_Iterator {d_Iterator();d_Iterator(const d_Iterator<T> &i2);~d_Iterator();operator=(const d_Iterator<T> &i2); operator==(const d_Iterator<T> &il, ■const d_Iterator<T> &i2);operator!=(const d_Iterator<T> &il,const d_Iterator<T> &i2); reset();not_done() const;advance();operator++();operator++(int);operator--();operator--(int);get_element() const;operator*() const;replace_element(const T &val);next(T &val);d_Iterator {d_Iterator();d_Iterator(const d_Iterator<T> &i2);~d_Iterator();operator=(const d_Iterator<T> &i2); operator==(const d_Iterator<T> &il, ■const d_Iterator<T> &i2);operator!=(const d_Iterator<T> &il,const d_Iterator<T> &i2); reset();not_done() const;advance();operator++();operator++(int);operator--();operator--(int);get_element() const;operator*() const;replace_element(const T &val);next(T &val);
template <class Т> class
template <class Т> class
public:
public:
d_Iterator<T> & friend d_Boolean
d_Iterator<T> & friend d_Boolean
friend d_Boolean
void d_Boolean void d_Iterator<T> & d_Iterator<T> d_Iterator<T> & d_Iterator<T> T
T void d_Boolean };
friend d_Boolean
void d_Boolean void d_Iterator<T> & d_Iterator<T> d_Iterator<T> & d_Iterator<T> T
T void d_Boolean };
d_Iterator {
d_Iterator();
d_Iterator(const d_Iterator<T> &i2);
~d_Iterator();
operator=(const d_Iterator<T> &i2); operator==(const d_Iterator<T> &il, ■const d_Iterator<T> &i2);
operator!=(const d_Iterator<T> &il,
const d_Iterator<T> &i2); reset();
not_done() const;
advance();
operator++();
operator++(int);
operator--();
operator--(int);
get_element() const;
operator*() const;
replace_element(const T &val);
next(T &val);
d_Iterator {
d_Iterator();
d_Iterator(const d_Iterator<T> &i2);
~d_Iterator();
operator=(const d_Iterator<T> &i2); operator==(const d_Iterator<T> &il, ■const d_Iterator<T> &i2);
operator!=(const d_Iterator<T> &il,
const d_Iterator<T> &i2); reset();
not_done() const;
advance();
operator++();
operator++(int);
operator--();
operator--(int);
get_element() const;
operator*() const;
replace_element(const T &val);
next(T &val);
Описание
d_Iterator()
Нулевой конструктор инициализирует объект итератора, который нельзя использовать для итерации, поскольку он еще не ссылается на конкретную коллекцию. Этому объекту нужно сначала присвоить итератор, связанный с некоторой коллекцией. Эта функция поддерживается для совместимости с итераторами библиотеки STL.
d_Iterator(const d_Iterator<T> &i2)
Конструктор копии инициализирует итератор значением, равным значению итератора, заданного параметром i2. Эта функция поддерживается для совместимости с итераторами STL.
~d_Iterator()
Деструктор класса.
d_Iterator<T> &
operator=(const d_Iterator<T> &i2)
Итератор присваивания используется для присвоения значения одного итератора другому. Эта функция поддерживается для совместимости с итераторами STL.
friend d_Boolean
operator==(const d_Iterator<T> &il, const d_Iterator<T> &i2)
Этот булев оператор равенства возвращает истинное значение, если равны два сравниваемых итератора, т.е. они ссылаются на один и тот же элемент в одной и той же коллекции; в противном случае он возвращает ложное значение. Эта функция поддерживается для совместимости с итераторами STL. Если делается попытка сравнить на равенство итераторы из различных коллекций, возбуждается исключениеd_Error_IteratorDifferentCollections.
friend d_Boolean
operator!=(const d__Iterator<T> &il, const d_Iterator<T> &i2)
Этот булев оператор равенства возвращает истинное значение, если два сравниваемых итератора не равны, т.е. они ссылаются на различные элементы; в противном случае он возвращает ложное значение. Эта функция поддерживается для совместимости с итераторами STL. Если делается попытка сравнить на равенство итераторы из различных коллекций, возбуждается исключение d_Error_IteratorDifferentCollections.
void
reset ()
Функция reset используется для установки итератора на начальный элемент итерации в данной коллекции.
d_Boolean
not_done() const
Эту булеву функцию можно вызвать для определения, завершена ли итерация. Она возвращает истинное значение, если еще остались элементы, подлежащие итерированию.
void advance()
Эта функция перемещает итератор на следующий элемент итерации.
d_Iterator<T> & operator++()
Префиксный оператор инкрементирования перемещает итератор к следующему элементу итерации. Он возвращает ссылку на объект итератора. Если эта функция вызывается после того, как итератор достигнет конца итерации, возбуждается исключение d_Error_IteratorExhausted. Эта функция поддерживается для совместимости с итераторами STL.
d_Iterator<T> operator++(int)
Постфиксный оператор инкрементирования перемещает итератор к следующему элементу итерации. Он возвращает значение исходного итератора, которое существовало до операции инкрементирования. Если эта функция вызывается после того, как итератор достигнет конца итерации, возбуждается исключение d_Error_IteratorExhausted. Эта функция поддерживается для совместимости с итераторами STL.
d_Iterator<T> &
operator—()
Префиксный оператор декрементирования перемещает итератор к предыдущему элементу итерации, т.е. назад на одну позицию. Он возвращает ссылку на объект итератора. Если эта функция вызывается после того, как итератор достигнет конца обратной итерации (при этом итератор расположен на первом элементе), возбуждается исключение d_Error_lteratorExhausted. Эта функция поддерживается для совместимости с итераторами STL.
Эту функцию нельзя вызывать для итераторов неупорядоченных коллекций (например, d_Set<T> или d_Bag<T>). При попытке сделать это будет сгенерировано исключениеd_Error_IteratorNotBackward.
d_Iterator<T> operator--(int)
Постфиксный оператор декрементирования перемещает итератор к предыдущему элементу итерации. Он возвращает значение исходного итератора, которое существовало до операции инкрементирования. Если эта функция вызывается после того, как итератор достигнет конца обратной итерации, возбуждается исключение d_Error_IteratorExhausted. Эта функция поддерживается для совместимости с итераторами STL.
Эту функцию нельзя вызывать для итераторов неупорядоченных коллекций (например, d_Set<T> или d_Bag<T>). При попытке сделать это будет сгенерировано исключение d_Error_IteratorNotBackward.
Т
get_element() const
Функция возвращает текущий элемент итерации по значению. Если при вызове этой функции не существует текущего элемента, возбуждается исключение d_Error_IteratorExhausted.
T
operator*() const
Функция возвращает текущий элемент итерации по значению. Эта функция поддерживается для совместимости с итераторами STL. Если при вызове этой функции не существует текущего элемента, возбуждается исключение d_Eгror_IteratorExhausted.
void
replace_element(const T &val)
Функция заменяет значение текущего элемента значением, заданным параметром val. Эту функцию можно использовать только с коллекцией d_List<T> или d_Varray<T>.
d_Boolean
next (Т &val)
Эту функцию можно использовать для проверки факта достижения конца итерации и возврата значения текущего элемента (если таковой существует) посредством параметра val. Если итератор расположен за последним элементом итерации, функция просто возвращает ложное значение. В противном случае функция присваивает значение текущего элемента параметру val, перемен тает итератор к следующему элементу и возвращает истинное значение.
Упорядоченная коллекция (список) элементов типа т, в которой разрешаются повторяющиеся элементы. Начальное значение индекса равно 0.
Синопсис
template <class Т> class d_List : public d_Collection<T> {
public:
d_List();
d_List(const d_List<T> SL2); ~d_List();
d_List<T> & Т
Т void void Т d_Boolean
operator™(const d_List<T> SL2); retrieve_first_element() const; retrieve_last_element() const;
remove_firse_element();
remove_last_element();
operator! ](unsigned long pos) const; find_element(const T sval,
unsigned long Spos) const;
Т void void
retrieve_element_at(unsigned long pos) const; remove_element_at(unsigned long pos);
replace_element_at(const T Sval, unsigned long pos);
// Следующая функция void void void void
унаследована:
insert_element(const T Sval);
insert_element_first(const T &val);
insert_element_last(const T sval); insert_element_after(const T & val, unsigned long pos);
void
insert_element_before(const T sval, unsigned long pos);
d_List<T> friend d_List<T>
concat(const d_List<T> SL2) const; operator*(const d_List<T> &L1, const d_List<T> &L2);
d_List<T> &
append(const d_List<T> &L2);
d List<T> & );
operator*™(const d_List<T> &L2);
Описание
d_List()
Нулевой конструктор инициализирует список без элементов.
d_List(const d_List<T> &L2)
Конструктор копии инициализирует объект, используя значения объектов в списке L2. Порядок элементов в инициализированном объекте совпадает с порядком элементов в списке L2. Для копирования элементов списка используется конструктор копии класса т.
~d_List()
Деструктор освобождает список, удаляя все его элементы. Если тип элементов списка имеет свой деструктор, он вызывается для каждого элемента.
d_List<T> &
operator=(const d_List<T> &L2)
Оператор присваивания удаляет исходные элементы из объекта и инициализирует объект, используя элементы списка L2. Порядок элементов в инициализированном объекте совпадает с порядком элементов в списке L2.
т
retrieve_first_element() const
Функция возвращает значение первого элемента в списке.
т
retrieve_last_eleinent () const
Функция возвращает значение последнего элемента в списке.
void
remove_first_element()
Функция удаляет первый элемент в списке.
void
remove_last_element()
Функция удаляет последний элемент в списке.
т
operator!](unsigned long pos) const
Функция возвращает значение элемента, расположенного в позиции роз. Эта Функция аналогична функции retrieve_element_at.
d_Boolean
find__element (const T &val, unsigned long &pos) const
Эта булева функция делает попытку найти элемент со значением val. Если попытка оказалась удачной, функция возвращает истинное значение; в противном случае — ложное. Если элемент со значением val обнаружен, его позиция возвращается посредством параметра роз.
т
retrieve_element_at(unsigned long роз) const
Функция возвращает значение элемента, расположенного в позиции роз. Эта функция аналогична оператору operator [ ].
void
remove_element_at(unsigned long pos)
Эта функция удаляет элемент, расположенный в позиции pos.
void
replace_element_at(const T &val, unsigned long pos)
Функция заменяет значение элемента, расположенного в позиции pos, значением val.
void
insert_element(const T &val)
Функция вставляет элемент со значением val в конец списка.
void
insert_element_first(const T &val)
Функция вставляет элемент со значением val в начало списка.
void
insert_element_last(const T &val)
Функция вставляет элемент co значением val в конец списка.
void
insert—element_after(const T &val, unsigned long pos)
Функция вставляет новый элемент со значением val после элемента, расположенного в позиции роз.
void
insert—element_before(const T &val, unsigned long pos)
Функция вставляет новый элемент со значением val перед элементом, расположенным в позиции роз.
d_List<T>
concat(const d_List<T> &L2) const
Функция создает и возвращает по значению новый объект класса d_List<T>, который представляет собой конкатенацию, включающую копии значений элементов объекта списка, за которыми последуют элементы списка L2. Ту же операцию выполняет функция operator+. Ниже приводится пример синтаксиса.
Iist3 = listl.concat(list2)
friend d_List<T>
operator+(const d_List<T> &L1, const d_List<T> &L2)
Функция создает и возвращает по значению новый объект класса d_List<T>, который образуется путем присоединения списка L2 к концу списка ы. Копии значений элементов, содержащихся в списках Ы и L2, помешаются в список результата. Эта функция выполняет ту же операцию, что и функция concat. Ниже приводится пример синтаксиса.
Iist3 = listl + list2.
d_List<T> &
append(const d_List<T> &L2)
Функция присоединяет копии значений элементов списка L2 к концу объекта и возвращает ссылку на этот объект списка. Эта функция выполняет ту же операцию, что и функция operator+=. Ниже приводится пример синтаксиса.
listl.append(list2);
d_List<T> &
operator+=(const d_List<T> &L2)
Функция присоединяет копии значений элементов списка L2 к концу объекта списка и возвращает ссылку на этот объект списка. Эта функция выполняет ту же операцию, что и функция append. Ниже приводится пример синтаксиса.
listl += list2;
Имя: d_ObjBCt
Назначение этого базового класса — сделать любой производный класс перманентным.
Синопсис
class d_Object { public:class d_Object { public:virtual d_Object & void void * void *virtual d_Object & void void * void *void *void virtual void virtual void };void *void virtual void virtual void };d_Object() ;d_Object(const d_Object &obj);~d_Object();operator=(const d_Object &obj);mark_modified() ;operator new(size_t size);operator new(size_t size, const d_Ref_Any sclusterObj, const char *typename);operator new(size_t size, d_Database *db, const char *typename);operator delete(void *);d_activate();d_deactivate();d_Object() ;d_Object(const d_Object &obj);~d_Object();operator=(const d_Object &obj);mark_modified() ;operator new(size_t size);operator new(size_t size, const d_Ref_Any sclusterObj, const char *typename);operator new(size_t size, d_Database *db, const char *typename);operator delete(void *);d_activate();d_deactivate();
class d_Object { public:
class d_Object { public:
virtual d_Object & void void * void *
virtual d_Object & void void * void *
void *
void virtual void virtual void };
void *
void virtual void virtual void };
d_Object() ;
d_Object(const d_Object &obj);
~d_Object();
operator=(const d_Object &obj);
mark_modified() ;
operator new(size_t size);
operator new(size_t size, const d_Ref_Any sclusterObj, const char *typename);
operator new(size_t size, d_Database *db, const char *typename);
operator delete(void *);
d_activate();
d_deactivate();
d_Object() ;
d_Object(const d_Object &obj);
~d_Object();
operator=(const d_Object &obj);
mark_modified() ;
operator new(size_t size);
operator new(size_t size, const d_Ref_Any sclusterObj, const char *typename);
operator new(size_t size, d_Database *db, const char *typename);
operator delete(void *);
d_activate();
d_deactivate();
Описание
d_Object()
Нулевой конструктор инициализирует объект.
d_Object(const d_Object &obj )
Конструктор копии инициализирует объект значением объекта ob j.
virtual ~d_Object()
Деструктор удаляет объект из кэша клиента и базы данных.
d_Object &
operator=(const d_Object &obj)
Оператор присваивает объекту значение объекта ob j.
void
mark_modified()
Эта функция должна быть вызвана перед любой модификацией объекта, чтобы установить признак измененения объекта. Реализация базы данных использует эту информацию, чтобы наложить необходимые блокировки и записать модифицированный объект в базу данных по завершении транзакции. Вызов этой функции для транзитного объекта не имеет никакого эффекта.
void *
operator new(size_C size)
Оператор new используется для создания транзитного экземпляра класса, выведенного из класса d_object. Предположим, что в классе Department опреде-
лен нулевой конструктор, который вызывается при создании нового экземпляра этого класса.
Department *dept = new Department!);
void *
operator new(size_t size, const d_Ref_Any &clusterObj, const char *typename)
Оператор new создает перманентный экземпляр класса с именем typename, который должен быть кластеризирован рядом с объектом, адресуемым параметром clusterObj.
void *
operator new(size_t size, d_Database *db, const char *typename)
Этот оператор new создает экземпляр класса с именем typename в базе данных db. Если для аргумента db указан тип d_Database:: transient_memory, то создаваемый экземпляр будет транзитным. В противном случае в базе данных db будет создан перманентный объект.
void
operator delete(void *)
Оператор delete используется для удаления из базы данных объекта, от которого зависит успех завершения транзакции.
virtual void deactivate()
Виртуальная функция d activate вызывается каждый раз. когда объект помещается в кэщ приложения. Приложение может определить эту функцию в классе, выведенном из класса d Object, для любой инициализации объекта. Если в приложении эта виртуальная функция не определена, ни одна операция не выполняется.
virtual
void d_deactivate()
Виртуальная функция d deactivate вызывается каждый раз. когда объект покидает кэш приложения. Любое приложение может определить эту функцию в классе, выведенном из класса d_Object, для операций, связанных с освобождением объекта. Если в приложении эта виртуальная функция не определена, ни одна операция не выполняется.
class d_OQL_Query { public:class d_OQL_Query { public:d_OQL_Query & voidd_OQL_Query & voidfriend d_OQL_Query & friend d_OQL_Query s friend d_OQL_Query s friend d_OQL_Query s friend d_OQL_Query s friend d_OQL_Query s friend d_OQL_Query s friend d_OQL_Query s friend d_OQL_Query s friend d_OQL_Query s friend d_OQL_Query s friend d_OQL_Query s friend d_OQL_Query s friend d_OQL_Query s friend d_OQL_Query Sfriend d_OQL_Query &templatecclass T> friend d_OQL_Query stemplatecclass T> friend d_OQL_Query s};templatecclass T> void templatecclass T> voidfriend d_OQL_Query & friend d_OQL_Query s friend d_OQL_Query s friend d_OQL_Query s friend d_OQL_Query s friend d_OQL_Query s friend d_OQL_Query s friend d_OQL_Query s friend d_OQL_Query s friend d_OQL_Query s friend d_OQL_Query s friend d_OQL_Query s friend d_OQL_Query s friend d_OQL_Query s friend d_OQL_Query Sfriend d_OQL_Query &templatecclass T> friend d_OQL_Query stemplatecclass T> friend d_OQL_Query s};templatecclass T> void templatecclass T> voidd_oql_execute(d_OQL_Query Sq, T Sr);d_oql_execute(d_OQL_Query sq, d_IteratorcT> sr);d_oql_execute(d_OQL_Query Sq, T Sr);d_oql_execute(d_OQL_Query sq, d_IteratorcT> sr);
class d_OQL_Query { public:
class d_OQL_Query { public:
d_OQL_Query & void
d_OQL_Query & void
friend d_OQL_Query & friend d_OQL_Query s friend d_OQL_Query s friend d_OQL_Query s friend d_OQL_Query s friend d_OQL_Query s friend d_OQL_Query s friend d_OQL_Query s friend d_OQL_Query s friend d_OQL_Query s friend d_OQL_Query s friend d_OQL_Query s friend d_OQL_Query s friend d_OQL_Query s friend d_OQL_Query S
friend d_OQL_Query &
templatecclass T> friend d_OQL_Query s
templatecclass T> friend d_OQL_Query s
};
templatecclass T> void templatecclass T> void
friend d_OQL_Query & friend d_OQL_Query s friend d_OQL_Query s friend d_OQL_Query s friend d_OQL_Query s friend d_OQL_Query s friend d_OQL_Query s friend d_OQL_Query s friend d_OQL_Query s friend d_OQL_Query s friend d_OQL_Query s friend d_OQL_Query s friend d_OQL_Query s friend d_OQL_Query s friend d_OQL_Query S
friend d_OQL_Query &
templatecclass T> friend d_OQL_Query s
templatecclass T> friend d_OQL_Query s
};
templatecclass T> void templatecclass T> void
d_oql_execute(d_OQL_Query Sq, T Sr);
d_oql_execute(d_OQL_Query sq, d_IteratorcT> sr);
d_oql_execute(d_OQL_Query Sq, T Sr);
d_oql_execute(d_OQL_Query sq, d_IteratorcT> sr);
Этот класс используется для построения и выполнения OQL-запросов под управлением С++-программ. Оператор сдвига << перегружается, чтобы разрешить как инкрементную конструкцию запроса, так и передачу значений аргументов в запрос. В классе предусмотрено два конструктора, которым передается строковый аргумент, содержащий запрос. Запрос в этой строке может содержать параметры в форме $i, где i — число, относящееся к /-му последующему операнду, расположенному справа от очередного вызова оператора сдвига влево «. Значение, переданное в первом вызове оператора сдвига влево «, заменит все экземпляры параметра $1 в строке. Если при вызове функции d_oql_execute окажется, что в строке запроса существует такой компонент $1, который не имеет соответствующей инициализации посредством операции сдвига влево, возбуждается исключение d_Error_QueryParameter- Countlnvalid. Если какой-либо аргумент имеет неверный тип, генерируется исключение d_Error_QueryParameterType!nvalid. После обращения к функции d_oql_execute начальная строка запроса остается, но операнды необходимо представлять заново.
Синопсис
d_OQL_Query() ;
d_OQL_Query(const char *s) ;
d_OQL_Query(const d_String &s);
d_OQL_Query(const d_OQL_Query Sq2) ;
~d_OQL_Query() ;
operator=(const d_OQL_Query Sq2);
clear!); // добавлено в ODMG 2.0
operatorcc(d_OQL_Query Sq, const char *s); operatorcc(d_OQL_Query Sq, const d_String ss); operatorcc(d_OQL_Query Sq, d_Char c) ;
operatorcc(d_OQL_Query sq, dOctet o) ; operatorcc(d_OQL_Query Sq, d Short s); operatorcc(d_OQL_Query Sq, d_UShort us); operatorcc(d_OQL_Query Sq, int i) ;
operatorcc(d_OQL_Query &q, unsigned int ui) ;
operatorcc(d_OQL_Query Sq, d_Long L); operatorcc(d_OQL_Query Sq, d_ULong UL) ; operatorcc(d_OQL_Query Sq, d_Float f) ; operatorcc(d_OQL_Query Sq, d_Double d) ; operatorcc(d_OQL_Query &q, const d_Date 4d); operatorcc(d_OQL_Query &q, const d_Time &t) ; operatorcc(d_OQL_Query &q,
const d_Timestamp &ts) ; operatorcc(d_OQL_Query &q,
const d_Interval &i) ;
operatorcc(d_OQL_Query Sq, const d_RefcT> Sr) ;
operatorcc(d_OQL_Query &q, const d_Collection &c) ;
Описание
d_OQL_Query()
Нулевой конструктор инициализирует объект запроса.
d_OQL_Query(const char *s) ;
Строковый аргумент s содержит OQL-запрос, который инициализирует объект запроса.
d_OQL_Query(const d_String &s)
Строка, содержащаяся в параметре s, представляет собой запрос, который используется для инициализации объекта запроса.
d_OQL_Query(const d_OQL_Query &q2)
Конструктор копии инициализирует объект запроса в соответствии с состоянием объекта запроса, заданным параметром q2.
~d_OQL_Query()
Деструктор удаляет объект запроса, освобождая все вторичные структуры данных.
d_OQL_Query &
operator=(const d_OQL_Query &q2);
Оператор присваивает объекту состояние объекта запроса, заданное параметром q2.
void clear();
Функция очищает любые параметры запроса, которые задаются посредством операторов «, и переводит объект запроса в состояние приема первого аргумента запроса. Эта функция-член была добавлена в ODMG 2.0.
friend d_OQL_Query &
operator<<(d_OQL_Query &q, const char *s)
Оператор << заменяет строкой s следующий неинициализированный параметр запроса, если таковой существует, и возвращает ссылку на объект запроса q.
friend d_OQL_Query &
operator<<(d_OQL_Query &q, const d_String &s)
Оператор << заменяет строкой s типа d String следующий неинициализированный параметр запроса, если таковой существует, и возвращает ссылку на объект запроса q.
friend d_OQL_Query &
operator<<(d_OQL_Query &q, d_Char c)
Оператор << заменяет символом с следующий неинициализированный параметр запроса, если таковой существует, и возвращает ссылку на объект запроса q.
friend d_OQL_Query &
operator<<(d_OQL_Query &q, d_Octet o)
Оператор « заменяет объектом типа d_Octet следующий неинициализированный параметр запроса, если таковой существует, и возвращает ссылку на объект запроса q.
friend d_OQL_Query &
operator<<(d_OQL_Query &q, d_Short s)
Оператор « заменяет коротким целым s следующий неинициализированный параметр запроса, если таковой существует, и возвращает ссылку на объект запроса q.
friend d_OQL_Query &
operator<<(d_OQL_Query &q, d_UShort us)
Оператор « заменяет коротким целым us без знака следующий неинициализированный параметр запроса, если таковой существует, и возвращает ссылку на объект запроса q.
friend d_OQL_Query &
operator<<(d_OQL_Query &q, int i)
Оператор << заменяет целым i следующий неинициализированный параметр запроса, если таковой существует, и возвращает ссылку на объект запроса q.
friend d_OQL_Query &
operator<<(d_OQL_Query &q, unsigned int ui)
Оператор « заменяет целым ui без знака следующий неинициализированный параметр запроса, если таковой существует, и возвращает ссылку на объект запроса q.
friend d_OQL_Query &
operator«(d_OQL_Query &q, d_Long L)
Оператор << заменяет длинным целым L следующий неинициализированный параметр запроса, если таковой существует, и возвращает ссылку на объект запроса q.
friend d_OQL_Query &
operator<<(d_OQL_Query &q, d_ULong UL)
Оператор << заменяет длинным целым UL без знака следующий неинициализированный параметр запроса, если таковой существует, и возвращает ссылку на объект запроса q.
friend d_OQL_Query &
operator<<(d_OQL_Query &q, d_Float f)
Оператор « заменяет вещественным значением f следующий неинициализированный параметр запроса, если таковой существует, и возвращает ссылку на объект запроса q.
friend d_OQL_Query &
operator<<(d_OQL_Query &q, d_Double d)
Оператор << заменяет вещественным значением двойной точности d следующий неинициализированный параметр запроса, если таковой существует, и возвращает ссылку на объект запроса q.
friend d_OQL_Query &
operator<<(d_OQL_Query &q, const d_Date &d)
Оператор << заменяет объектом d класса d_Date следующий неинициализированный параметр запроса, если таковой существует, и возвращает ссылку на объект запроса q.
friend d_OQL_Query &
operater«(d_OQL_Query &q, const d_Time &t)
Оператор « заменяет объектом t класса d_Time следующий неинициализированный параметр запроса, если таковой существует, и возвращает ссылку на объект запроса q.
friend d_OQL_Query &
operator<<(d_OQL_Query &q, const d_Timestamp Sts)
Оператор << заменяет объектом ts класса d Timestamp следующий неинициализированный параметр запроса, если таковой существует, и возвращает ссылку на объект запроса q.
friend d_OQL_Query &
operator«(d_OQL_Query &q, const d_Interval &i)
Оператор « заменяет интервалом времени i (объектом класса d lnterval) следующий неинициализированный параметр запроса, если таковой существует, и возвращает ссылку на объект запроса q.
friend template<class Т> d_OQL_Query &
operator<<(d_OQL_Query &q, const d_Ref<T> &r)
Оператор << заменяет ссылкой г на объект типа Т (объектом класса d_Interval) следующий неинициализированный параметр запроса, если таковой существует, и возвращает ссылку на объект запроса q.
friend template<class Т> d_OQL_Query &
operator<<(d_OQL_Query &q, const d_Collection<T> &c)
Оператор << заменяет ссылкой с на коллекцию элементов типа т (объектом класса d_Interval) следующий неинициализированный параметр запроса, если таковой существует, и возвращает ссылку на объект запроса q.
template<class Т>
void d_oql_execute(d_OQL_Query &q, T &r)
Функция выполняет запрос q, а результат размещается в параметре результата г. При успешном выполнении запроса его параметры очищаются. Если же возникает ошибочная ситуация и запрос не может выполниться успешно (или возбуждается исключение), параметры запроса не очищаются. Для очистки всех параметров запроса приложение может вызвать функцию clear.
template<class Т>
void d_oql_execute(d_OQL_Query &q, d_Iterator<T> &r)
Функция выполняет запрос q. Вместо того чтобы поместить полный результат в параметр результата г, как в предыдущей функции, итератор, адресуемый параметром г, инициализируется таким образом, чтобы он ссылался на первый элемент результата. Затем приложение может опросить все элементы результата. Эта функция обеспечивает курсороподобный инструмент базы данных и, в отличие от предыдущей функции, не требует, чтобы законченный результат был реализован в виде коллекции. Данная функция позволяет передавать приложению результаты небольшими порциями, по мере возникновения потребности в этих данных..
Ссылка на экземпляр потенциально перманентного класса т.
Синопсис
template <class Т> class d_Ref {
public:
d_Ref() ;
d_Ref(T *p) ;
d_Ref(const d_Ref<T> Sr2);
d_Ref(const d_Ref_Any Sr2);
~d_Ref();
operator d_Ref_Any() const;
d_Ref<T> & d_Ref<T> & void т * т & т 4 void
operator=(T *p) ;
operator=(const d_Ref<T> sr2); clear();
operator->() const;
operator*() const;
ptr() const;
delete_object();
Булевы предикаты для проверки ссылок
d_Boolean d_Boolean
operator!() const; is_null() const;
// Ссылаются ли эти ссылки (d_Ref) и указатели на
// одни и те же объекты? friend d_Boolean
operator==(const d_Ref<T> Srl, const d_Ref<T> Sr2);
friend d_Boolean friend d_Boolean friend d_Boolean
operator==(const d_Ref<T> Srl, const T *p); operator==(const T *p, const d_Ref<T> Sr2); operator==(const d_Ref<T> Srl,
const d_Ref_Any Sr2);
friend d_Boolean
operator!=(const d_Ref<T> Srl, const d_Ref<T> sr2) ;
friend d_Boolean friend d_Boolean friend d_Boolean
operator!=(const d_Ref<T> srl, const T *p); operator!=(const T *p, const d_Ref<T> Srl); operator!=(const d_Ref<T> Srl,
const d_Ref_Any Sr2);
Описание
d_Ref()
Нулевой конструктор инициализирует ссылку нулевым значением.
d_Ref(Т *р)
Конструктор инициализирует ссылку, чтобы она ссылалась на экземпляр класса Т, адресуемый указателем р. Значение 0 интерпретируется как нулевая ссылка.
d_Ref(const d_Ref<T> &r2)
Конструктор копии инициализирует ссылку значением ссылки г2.
d_Ref(const d_Ref_Any &r2)
Конструктор копии инициализирует ссылку значением ссылки г2. Если г2 не ссылается на экземпляр класса т или любой класс, открыто (с помощью спе-
цификатора доступа public) выведенный из класса Т, возбуждается исключение d_Error_Type!nvalid.
~d_Ref()
Деструктор удаляет ссылку, но адресуемый ею объект не удаляется.
operator d_Ref_Any() const
Эта функция преобразует экземпляр класса d_Ref<T> в экземпляр класса d_Ref_Any.
d_Ref<T> &
operator=(T *р)
Функция присваивает ссылке значение, позволяющее ей ссылаться на экземпляр класса т, адресуемый указателем р. Функция возвращает ссылку на объект класса d_Ref<T>. Она используется для преобразования указателя (т *р) в ссылку (d_Ref<T>) на объект. См. раздел 6.1.1.
d_Ref<T> &
operator=(const d_Ref<T> &r2)
Этот оператор присваивает данной ссылке значение ссылки г2 и возвращает ссылку на объект класса d_Ref<T>.
void.
clear()
Эта функция очищает ссылку, устанавливая ее равной null-значению.
т *
operator->() const
Функция возвращает указатель на экземпляр класса т, адресуемый данным выражением. Этот указатель действителен до завершения транзакции либо до удаления ссылки d_Ref<T> или адресуемого ею объекта. Если при выполнении операции эта ссылка становится равной null-значению, возбуждается исключение d_Error_RefNull.
Т &
operator*() const
Функция возвращает С++-ссылку на экземпляр класса т, адресуемый данным выражением. Возвращаемое выражение действительно до завершения транзакции либо до удаления ссылки d_Ref<T> или адресуемого ею объекта. Если при выполнении операции эта ссылка становится равной null-значению, возбуждается исключение d_Error_RefNull.
т *
ptr () const
Функция преобразует ссылку в указатель на экземпляр класса т. Этот указатель действителен до завершения транзакции либо до удаления самого объекта. Если при выполнении операции данная ссылка становится равной null-значению, возбуждается исключение d_Error_RefNull.
void
delete_object()
Функция удаляет адресуемый объект. Если при выполнении операции данная ссылка становится равной null-значению, возбуждается исключение d_Error_RefNull.
d_Boolean
is_null() const
Если данная ссылка является нулевой, эта булева функция возвращает истинное значение. Если же ссылка ссылается на некоторый объект, функция возвращает ложное значение.
friend d_Boolean
operator==(const d_Ref<T> &rl, const T *p)
friend d_Boolean
operator==(const d_Ref<T> &rl, const T *p)
friend d_Boolean
operator==(const T *p, const d_Ref<T> &r2)
friend d_Boolean
operator==(const d_Ref<T> &rl, const d_Ref_Any &r2)
Эти булевы операторы равенства возвращают истинное значение, если два операнда равны, т.е. они или ссылаются на один и тот же экземпляр, или оба имеют нулевые значения. В противном случае операторы возвращают ложное значение.
friend d_Boolean
operator!=(const d_Ref<T> &rl, const d_Ref<T> &r2)
friend d_Boolean
operator!=(const d_Ref<T> &rl, const T *p)
friend d_Boolean
operator!=(const T *p, const d_Ref<T> &rl)
friend d_Boolean
operator!=(const d_Ref<T> &rl, const d_Ref_Any &r2)
Эти булевы операторы неравенства возвращают истинное значение, если операнды ссылаются на различные экземпляры или только один из операндов имеет нулевое значение. В противном случае операторы возвращают ложное значение.
Имя: d_Ref_Ang
Обобщенная ссылка на экземпляр любого потенциально перманентного класса,
Синппсисclass d_Ref_Any { public:Синппсисclass d_Ref_Any { public:d_Ref_Any & d_Ref_Any & void voidd_Ref_Any & d_Ref_Any & void voidт.е. любого класса, производного от класса d_Object.
Синппсис
class d_Ref_Any { public:
Синппсис
class d_Ref_Any { public:
d_Ref_Any & d_Ref_Any & void void
d_Ref_Any & d_Ref_Any & void void
d_Ref_Any() ;
d_Ref_Any(const d_Ref_Any sr2);
d_Ref_Any(d_Object *p);
~d_Ref_Any();
operator®(const d_Ref_Any &r2);
operator®(d_Object *p);
clear();
delete_object() ;
// Булевы предикаты для проверки на равенство данного // значения нулю
d_Booleanoperator!() const;
d_Booleanis_null() const;
friend d_Boolean friend d_Boolean friend d_Boolean friend d_Boolean friend d_Boolean friend d_Boolean ) :friend d_Boolean friend d_Boolean friend d_Boolean friend d_Boolean friend d_Boolean friend d_Boolean ) :operator®®(const d_Ref_Any srl, const d_Ref_Any 4r2);
friend d_Boolean friend d_Boolean friend d_Boolean friend d_Boolean friend d_Boolean friend d_Boolean ) :
friend d_Boolean friend d_Boolean friend d_Boolean friend d_Boolean friend d_Boolean friend d_Boolean ) :
operator®®(const d_Ref_Any &rl, const d_Object *p) ;
operator®®(const d_Object *p, const d_Ref_Any Sr2);
operator!=(const d_Ref_Any Srl, const d_Ref_Any &r2);
operator!=(const d_Ref_Any &rl, const d_Object *p) ;
operator ! = (const d_Object *p, const d_Ref_Any Sr2);
Описание
d_Ref_Any( )
Нулевой конструктор инициализирует объектную ссылку null-значением.
d_Ref_Any(const d_Ref_Any &r2)
Конструктор копии инициализирует объект значением ссылки г2. Этот объект либо ссылается на экземпляр класса, выведенного из класса d_Object, либо имеет null-значение.
d_Ref_Any(d_Object *р)
Этот конструктор инициализирует объект таким образом, чтобы он ссылался на объект, адресуемый указателем р. Нулевое значение указателя инициализирует и ссылку нулевым значением.
~d_Ref_Any()
Деструктор удаляет экземпляр класса d_Ref_Any, но адресуемый объект при этом не удаляется.
d_Ref_Any &
operator=(const d_Ref_Any &r2)
Этот оператор присваивает объекту значение ссылки г2.
d_Ref_Any &
operator=(d_Object *р)
Этот оператор присваивает объекту значение ссылки на объект, адресуемый указателем р.
void clear()
Эта функция устанавливает объект равным null-значению ссылки.
void
delete_object()
Эта функция удаляет объект, адресуемый данной ссылкой.
d_Boolean
operator!() const
Если данная ссылка имеет null-значение, эта булева функция возвращает истинное значение. Если же данный объект ссылки ссылается на некоторый экземпляр, функция возвращает ложное значение.
d_Boolean
is_null() const
Если данная ссылка является нулевой, эта булева функция возвращает истинное значение. Если же эта ссылка ссылается на некоторый объект, функция возвращает ложное значение.
friend d_Boolean
operator==(const d_Ref_Any &rl, const d_Ref_Any &r2)
friend d_Boolean
operators(const d_Ref_Any &rl, const d_Object *p)
friend d_Boolean
operator==(const d_Object *p, const d_Ref_Any &r2)
Эти булевы операторы равенства возвращают истинное значение, если два операнда равны, т.е. они или ссылаются на один и тот же экземпляр, или оба имеют нулевые значения. В противном случае операторы возвращают ложное значение.
friend d_Boolean
operator!=(const d_Ref_Any &rl, const d_Ref_Any &r2)
friend d_Boolean
operator!=(const d_Ref_Any &rl, const d_Object *p)
friend d_Boolean
operator!=(const d_Object *p, const d_Ref_Any &r2)
Эти булевы операторы неравенства возвращают истинное значение, если операнды ссылаются на различные экземпляры или только один из операндов имеет нулевое значение. В противном случае операторы возвращают ложное значение.
Этот шаблонный класс используется для представления позиционной двунаправленной связи типа “ко-многим” между классами Зит. Экземпляр класса d Rel_List<T, МТ> встраивается в класс S. Перманентный класс Т на другом конце двунаправленной связи должен иметь член типа d_Rel_List<S, MS>, d_Rel_Set<S,MS> или d_Rel_Ref<S,MS>. Второй шаблонный параметр мт имеет тип const char * и должен задавать имя некоторой строковой переменной. (Это должна быть именно строковая переменная, а не строковый литерал.) Эта строковая переменная должна содержать имя члена класса т, который ссылается на класс S. Аналогично этот член в классе т должен иметь второй шаблонный параметр, который ссылается на соответствующий член в классе s.
Пример заголовочного файла:
extern const char _listT[], _refS[];
class S { d_Rel_List<T,_refS> listT;
class T {-
d_Rel_X<S, _listT> refS; // X может быть Ref, List или Set
} ;
Пример программного файла:
const char _refS[] = "refS";
const char _listT[] = "listT";
Член на каждом конце связи ссылается на член другого конца связи.
Этот класс наследует свой интерфейс от базового класса d_List<d_Ref<T>>.
Синопсис
template <class Т, const
char *MT> class d_Rel_List : public d_List<d_Ref<T> >
{ public:
d_Rel_List() ;
d_Rel_List(const d_List<d_Ref<T> > &L2);
~d_Rel_List();
d_Rel_List<T,MT> s d_Ref<T> d_Ref<T> void void d_Ref<T> d_Boolean
operator=(const d_List<d_Ref<T> > &L2); retrieve_first_element() const;
retrieve_last_element() const;
remove_first_element();
remove_last_element();
operator[](unsigned long pos) const; find_element(const d_Ref<T> &val, unsigned long spos) const;
d_Ref<T> void void
retrieve_element_at(unsigned long pos) const;
remove_element_at(unsigned long pos);
replace_element_at(const d_Ref<T> &val,
unsigned long pos);
void void void
insert_element_first(const d_Ref<T> &val); insert_element_last(const d_Ref<T> &val); insert_element_after(const d_Ref<T> &val, unsigned long pos);
void
insert_element_before(const d_Ref<T> &val, unsigned long pos);
d List<d Ref<T> >
concat(const d_List<d_Ref<T> > &L2) const;
friend d_List<d_Ref<T> >d_Rel_List<T,MT> & d_Rel_List<T, MT> &friend d_List<d_Ref<T> >d_Rel_List<T,MT> & d_Rel_List<T, MT> &operator*(const d_Rel_List<T, MT> SL1, const d_Rel_List<T,MT> &L2);
friend d_List<d_Ref<T> >
d_Rel_List<T,MT> & d_Rel_List<T, MT> &
friend d_List<d_Ref<T> >
d_Rel_List<T,MT> & d_Rel_List<T, MT> &
append(const d_List<d_Ref<T> > SL2);
operator+=(const d_List<d_Ref<T> > &L2) ;
Оиисание
d_Rel_List()
Нулевой конструктор инициализирует список без элементов.
d_Rel_List(const d_Rel_List<d_Ref<Т> > &L2)
Конструктор копии инициализирует объект списка связей, используя ссылки на класс т, содержащиеся в списке L2. Элементы в инициализированном объекте располагаются в том же порядке, который присущ элементам списка L2. Каждый экземпляр класса т, на который есть ссылка в списке L2, имеет член обратной связи, модифицированный для добавления ссылки на экземпляр класса S, который содержит этот объект.
~d_Rel_List()
Деструктор отменяет инициализацию списка, удаляя все его элементы. Поскольку экземпляры класса d_Rel_List могут быть встроены только в потенциально перманентный класс, эту функцию можно вызывать только при удалении перманентного объекта типа S, включающего объект связи. Для каждого элемента списка соответствующий член (идентифицируемый шаблонным параметром мт) адресуемого экземпляра класса т будет модифицирован для удаления ссылки на экземпляр класса s, включающего этот объект.
d_Rel_List<T, МТ> &
operator=(const d_List<d_Ref<Т> > &L2)
Оператор присваивания удаляет текущие элементы из объекта списка и инициализирует этот объект элементами списка L2. Элементы в инициализированном объекте располагаются в том же порядке, который присущ элементам списка L2. Для всех исходных элементов (которые удаляются) член экземпляра класса Т (идентифицируемый шаблонным параметром мт) модифицируется для удаления ссылки на экземпляр класса S, который содержит этот объект. При добавлении элементов списка L2 к объекту соответствующий член (идентифицируемый шаблонным параметром мт) экземпляра класса т, адресуемого каждым элементом, модифицируется для ссылки на экземпляр класса S, включающего этот объект.
d_Ref<T>
retrieve_f1rst_element() const
Функция возвращает первый элемент списка.
d_Ref<T> retrieve_last_element () const
Функция возвращает последний элемент списка.
void
remove_first_element()
Функция удаляет первый элемент списка. Член (идентифицируемый шаблонным параметром мт) экземпляра класса т, адресуемого удаляемым элементом, модифицируется для удаления ссылки на экземпляр класса s, который содержит этот объект.
void
remove_last_element()
Функция удаляет последний элемент списка. Член (идентифицируемый шаблонным параметром МТ) экземпляра класса т, адресуемого удаляемым элементом, модифицируется для удаления ссылки на экземпляр класса S, который содержит этот объект.
d_Ref<T>
operator[](unsigned long pos) const
Оператор возвращает ссылку на элемент, расположенный в позиции роз.
d_Boolean
find_element(const d_Ref<T> &val, unsigned long &pos) const
Эта булева функция делает попытку отыскать элемент со значением val. Если поиск увенчается успехом, она возвратит истинное значение, в противном случае — ложное. В случае успешного поиска позиция найденного элемента возвращается посредством параметра pos.
d_Ref<T>
retrieve_element_at(unsigned long pos) const
Функция возвращает ссылку на элемент, расположенный в позиции pos.
void
remove_element_at(unsigned long pos)
Функция удаляет элемент, расположенный в позиции pos. Член (идентифицируемый шаблонным параметром мт) экземпляра класса т, адресуемого удаляемым элементом, модифицируется для удаления ссылки на экземпляр класса S, который содержит этот объект.
void
replace_element_at(const d_Ref<T> &val, unsigned long pos)
Эта функция заменяет значение элемента, расположенного в позиции pos, значением val. Член (идентифицируемый шаблонным параметром мт) экземпляра класса т, адресуемого заменяемым элементом, модифицируется для удаления ссылки на экземпляр класса S, который содержит этот объект. Член (идентифицируемый шаблонным параметром МТ) экземпляра класса т, адресуемого элементом со значением val, модифицируется для добавления ссылки на экземпляр класса S, который содержит этот объект.
void
insert_element_first(const d_Ref<T> &val)
Эта функция вставляет ссылку на экземпляр т в начало списка. Член (идентифицируемый шаблонным параметром МТ) экземпляра класса т, адресуемого вставляемым элементом, модифицируется для добавления ссылки на экземпляр класса S, который содержит этот объект.
void
insert_element_last(const d_Ref<T> &val)
Эта функция вставляет ссылку на экземпляр т в конец списка. Член (идентифицируемый шаблонным параметром мт) экземпляра класса т, адресуемого вставляемым элементом, модифицируется для добавления ссылки на экземпляр класса s, который содержит этот объект.
void
insert_element_after(const d_Ref<T> &val, unsigned long pos)
Эта функция вставляет ссылку на экземпляр т за позицией роз списка. Член (идентифицируемый шаблонным параметром мт) экземпляра класса т, адресуемого вставляемым элементом, модифицируется для добавления ссылки на экземпляр класса S, который содержит этот объект.
void
insert_element_before(const d_Ref<T> &val, unsigned long pos)
Эта функция вставляет ссылку на экземпляр Т перед позицией pos списка. Член (идентифицируемый шаблонным параметром мт) экземпляра класса т, адресуемого вставляемым элементом, модифицируется для добавления ссылки на экземпляр класса S, который содержит этот объект.
d_List<d_Ref<Т> >
concat(const d_List<d_Ref<T> > &L2) const
Эта функция создает и возвращает по значению новый объект класса d_List<d_Ref<T>>. Список содержит конкатенацию элементов данного объекта списка и элементов списка L2. В экземпляры, участвующие в двунаправленной связи, никаких изменений не вносится. Ниже приводится пример синтаксиса.
Iistl.concat(list2)
friend d_List<d_Ref<T> >
operator+(const d_Rel_List<T,MT> &L1, const d_Rel_List<T, MT> &L2 )
Эта функция использует оператор operator+ для создания и возврата нового объекта класса d_List<d_Ref<T>> по значению, которое представляет собой конкатенацию списка Ы и списка L2, присоединенного к концу списка L1. В экземпляры, участвующие в двунаправленной связи, никаких изменений не вносится.
d_Rel_List<T, МТ> &
append(const d_List<d_Ref<Т> > &L2)
Эта функция присоединяет элементы списка L2 к концу данного объекта списка и возвращает ссылку на этот объект. Член (идентифицируемый шаблонным параметром МТ) каждого экземпляра класса т, адресуемого элементом списка L2, модифицируется для добавления ссылки на экземпляр класса S, который содержит этот объект.
d_Rel_List<T, МТ> &
operator+=(const d_List<d_Ref<T> > &L2)
Эта функция выполняет ту же операцию, что и функция append. Элементы списка L2 присоединяются к концу данного объекта, и возвращается ссылка на этот объект. Член (идентифицируемый шаблонным параметром мт) каждого экземпляра класса Т, адресуемого элементом списка L2, модифицируется для добавления ссылки на экземпляр класса S, который содержит этот объект.
Имя: d RBl RBf<T, МТ>
Этот шаблонный класс представляет двунаправленную связь типа “к-одному” между классами Зит. Экземпляр класса d_Rel_Ref<T, МТ> встроен в класс S. Класс т на другом конце двунаправленной связи должен иметь член типа d_Rel_List<S, MS>, d_Rel_Set<S,MS> или d_Rel_Ref<S, MS>. Второй шаблонный параметр МТ должен представлять собой некоторую строковую переменную, содержащую имя члена класса т, который ссылается на класс S. Аналогично этот член в классе т должен иметь второй шаблонный параметр (MS), который ссылается на соответствующий член в классе S, т.е. на член, являющийся экземпляром класса d_Rel_Ref<T, МТ>.
Пример заголовочного файла.
extern const char _refT[], _refS[);
class S {
d_Rel_Ref<T,_refS> refT;
class T { d_Rel_X<S,_refT> refS; // X может быть Ref, List или Set
Пример программного файла.
const char _refS[] = "refS";
const char _refT[] = "refT";
Поскольку этот класс наследует свой интерфейс от базового класса d_Ref<T>, в приведенном интерфейсе основное внимание уделяется расширенному поведению класса d_Rel_Ref<T, МТ>, связанному с изменением ссылок для поддержки двунаправленной связи. Некоторые же унаследованные функции приведены здесь просто для полноты описания.
Синопспс
template <class Т, const char *MT> class d_Rel_Ref :
public d_Ref<T> { public:public d_Ref<T> { public:d_Rel Ref();d_Rel Ref();d_Rel_Ref<T,MT> 4 d_Rel_Ref<T,MT> 4 d_Rel_Ref<T,MT> 4 voidT *T 4T *voidd_Rel_Ref<T,MT> 4 d_Rel_Ref<T,MT> 4 d_Rel_Ref<T,MT> 4 voidT *T 4T *voidd_Rel_Ref (T *p) ;d_Rel_Ref(const d_Ref<T> 4r2);d_Rel_Ref(const d_Ref_Any 4r2); d_Rel_Ref(const d_Rel_Ref<T,MT> ~d_Rel_Ref();operator d_Ref_Any() const;operator™(T *p);operator™(const d_Ref<T> 4r2);operator™(const d_Rel_Ref<T,MT> clear();operator->() const;operator*;) const;ptr() const;delete_object();d_Rel_Ref (T *p) ;d_Rel_Ref(const d_Ref<T> 4r2);d_Rel_Ref(const d_Ref_Any 4r2); d_Rel_Ref(const d_Rel_Ref<T,MT> ~d_Rel_Ref();operator d_Ref_Any() const;operator™(T *p);operator™(const d_Ref<T> 4r2);operator™(const d_Rel_Ref<T,MT> clear();operator->() const;operator*;) const;ptr() const;delete_object();4г2) ;4г2) ;4г2) ;4г2) ;Булевы предикаты для проверки ссылокd_Booleanoperator!() const;d_Booleanis_null() const;Указывают ли эти ссылки (d_Ref) и указатели на одни и теБулевы предикаты для проверки ссылокd_Booleanoperator!() const;d_Booleanis_null() const;Указывают ли эти ссылки (d_Ref) и указатели на одни и теже объекты?же объекты?
public d_Ref<T> { public:
public d_Ref<T> { public:
d_Rel Ref();
d_Rel Ref();
d_Rel_Ref<T,MT> 4 d_Rel_Ref<T,MT> 4 d_Rel_Ref<T,MT> 4 void
T *
T 4
T *
void
d_Rel_Ref<T,MT> 4 d_Rel_Ref<T,MT> 4 d_Rel_Ref<T,MT> 4 void
T *
T 4
T *
void
d_Rel_Ref (T *p) ;
d_Rel_Ref(const d_Ref<T> 4r2);
d_Rel_Ref(const d_Ref_Any 4r2); d_Rel_Ref(const d_Rel_Ref<T,MT> ~d_Rel_Ref();
operator d_Ref_Any() const;
operator™(T *p);
operator™(const d_Ref<T> 4r2);
operator™(const d_Rel_Ref<T,MT> clear();
operator->() const;
operator*;) const;
ptr() const;
delete_object();
d_Rel_Ref (T *p) ;
d_Rel_Ref(const d_Ref<T> 4r2);
d_Rel_Ref(const d_Ref_Any 4r2); d_Rel_Ref(const d_Rel_Ref<T,MT> ~d_Rel_Ref();
operator d_Ref_Any() const;
operator™(T *p);
operator™(const d_Ref<T> 4r2);
operator™(const d_Rel_Ref<T,MT> clear();
operator->() const;
operator*;) const;
ptr() const;
delete_object();
4г2) ;
4г2) ;
4г2) ;
4г2) ;
Булевы предикаты для проверки ссылок
d_Booleanoperator!() const;
d_Booleanis_null() const;
Указывают ли эти ссылки (d_Ref) и указатели на одни и те
Булевы предикаты для проверки ссылок
d_Booleanoperator!() const;
d_Booleanis_null() const;
Указывают ли эти ссылки (d_Ref) и указатели на одни и те
же объекты?
же объекты?
friend d_Booleanfriend d_Boolean friend d_Boolean friend d_Booleanfriend d_Booleanfriend d_Booleanfriend d_Boolean friend d_Boolean friend d_Booleanfriend d Booleanfriend d_Booleanfriend d_Boolean friend d_Boolean friend d_Booleanfriend d_Booleanfriend d_Booleanfriend d_Boolean friend d_Boolean friend d_Booleanfriend d Booleanoperator==(const d_Rel_Ref<T,MT> Srl, const d_Rel_Ref<T,MT> Sr2);
friend d_Boolean
friend d_Boolean friend d_Boolean friend d_Boolean
friend d_Boolean
friend d_Boolean
friend d_Boolean friend d_Boolean friend d_Boolean
friend d Boolean
friend d_Boolean
friend d_Boolean friend d_Boolean friend d_Boolean
friend d_Boolean
friend d_Boolean
friend d_Boolean friend d_Boolean friend d_Boolean
friend d Boolean
operator==(const d_Rel_Ref<T,MT> Srl, const T *p); operator==(const T *p, const d_Rel_Ref<T,MT> Sr2); operator==(const d_Rel_Ref<T,MT> Srl,
const d_Ref_Any sr2);
operator==(const d_Ref_Any Srl,
const d_Rel_Ref<T,MT> Sr2);
operator!=(const d_Rel_Ref<T,MT> Srl,
const d_Rel_Ref<T,MT> Sr2);
operator!=(const d_Rel_Ref<T,MT> Srl, const T *p) ; operator!=(const T *p, const d_Rel_Ref<T,MT> Sr2); operator!=(const d_Rel_Ref<T,MT> Srl,
const d_Ref_Any Sr2);
operator!=(const d_Ref_Any Srl,
const d_Rel_Ref<T,MT> Sr2);
Описание
d_Rel_Ref()
Нулевой конструктор инициализирует ссылку нулевым значением.
d_Rel_Ref(T *р)
Этот конструктор инициализирует ссылку таким образом, чтобы она указывала на экземпляр класса т, адресуемый указателем р. Член экземпляра класса т, заданный параметром МТ, модифицируется для включения ссылки на экземпляр класса S, содержащий этот объект.
d_Rel_Ref(const d_Ref<T> &r2)
Этот конструктор копии инициализирует данную ссылку значением ссылки, заданной параметром г2. Член экземпляра класса т (в предположении, что ссылка г2 имеет ненулевое значение), заданный параметром МТ, модифицируется для включения ссылки на экземпляр класса S, содержащий этот объект.
d_Rel_Ref(const d_Ref_Any &r2)
Этот конструктор копии инициализирует ссылку значением ссылки, заданной параметром г2. Член экземпляра класса т, заданный параметром МТ, модифицируется для включения ссылки на экземпляр класса S, содержащий этот объект. Если г2 не ссылается на экземпляр класса т, возбуждается исключение d_Error_Type!nvalid.
~d_Rel_Ref()
Поскольку экземпляр этого класса должен быть встроен в перманентный объект, этот деструктор вызывается при удалении объекта, включающего объект связи, т.е. экземпляра класса S. Член экземпляра класса т, заданный параметром МТ, модифицируется для удаления ссылки на экземпляр класса S, содержащий этот объект.
operator d_Ref_Any() const■
Эта функция преобразует экземпляр класса d_Rel_Ref<T,MT> в экземпляр класса d Ref Any. При этом никаких действий, затрагивающих двунаправленную связь, не выполняется.
d_Rel_Ref<T, МТ> &■
operator=(T *р)
Этот оператор присваивает данному объекту значение ссылки на объект, адресуемый указателем р, и возвращает ссылку на объект связи. Эта функция выполняет два действия с двунаправленной связью. Если до операции объект ссылался на некоторый экземпляр класса т, т.е. не был нулевым, то член в экземпляре класса т, заданный параметром МТ, модифицируется для удаления ссылки на экземпляр класса S, содержащий этот объект. Затем (в предположении, что указатель р ненулевой) член в экземпляре класса т, адресуемый указателем р, модифицируется для включения ссылки на экземпляр класса S, который содержит этот объект.
d_Rel_Ref<T, МТ> &
operator=(const d_Ref<T> &r2)
Этот оператор присваивает данному объекту значение ссылки г2 и возвращает ссылку на объект связи. Эта функция выполняет два действия с двунаправленной связью. Если до операции объект ссылался на некоторый экземпляр класса Т, т.е. не был нулевым, то член в адресуемом экземпляре класса т, заданный параметром МТ, модифицируется для удаления ссылки на экземпляр класса S, содержащий этот объект. Затем (также в предположении, что ссылка г2 ненулевая) член в экземпляре класса Т, идентифицируемый параметром МТ, модифицируется для включения ссылки на экземпляр класса S, который содержит этот объект.
void clear ()
Эта функция очищает ссылку, устанавливая ее равной нулевому значению. Если удаляемая ссылка указывала на экземпляр класса т, то член в экземпляре класса т, заданный параметром МТ, модифицируется для удаления ссылки на экземпляр класса S, содержащий этот объект.
т *
operator->() const
Эта функция наследует поведение от класса d_Ref<T>. При этом никаких действий с двунаправленной связью не выполняется.
т &
operator*() const
Эта функция наследует поведение от класса d_Ref<T>. При этом никаких действий, затрагивающих двунаправленную связь, не выполняется.
т *
ptr () const
Эта функция наследует поведение от класса d_Ref<T>. При этом никаких действий с двунаправленной связью не выполняется.
void
delete_object()
Эта функция удаляет адресуемый объект. Обратите внимание, что в результате удаления экземпляра класса Т будет удален и его член, идентифицируемый параметром МТ, который содержит ссылку на экземпляр класса S, содержащий этот объект. При удалении экземпляра класса Т все объекты, участвующие в двунаправленной связи с этим экземпляром, должны быть обновлены для удаления своей ссылки на экземпляр класса т. Если при этом окажется, что ссылка нулевая, возбуждается исключение d_Error_RefNul.
d_Boolean
operator!() const
Эта функция наследует поведение от класса d_Ref<T>. При этом никаких действий, затрагивающих двунаправленную связь, не выполняется.
d_Boolean
is_null() const
Эта функция наследует поведение от класса d_Ref<T>. При этом никаких действий, затрагивающих двунаправленную связь, не выполняется. Поскольку класс d_Rel_Ref<T, МТ> открытым способом выведен из класса d_Ref<T>, следующие операторы равенства и неравенства, определенные для класса d_Ref<T>, можно использовать для экземпляров класса d_Rel_Ref<T,МТ> или d_Ref<T>.
friend d_Boolean
operator==(const d_Rel_Ref<T,MT> Srl, const d_Rel_Ref<T,MT> Sr2)
Этот булев оператор равенства возвращает истинное значение, если сравниваемые ссылки либо указывают на один и тот же экземпляр, либо являются нулевыми. В противном случае он возвращает ложное значение.
friend d_Boolean
operator==(const d_Rel_Ref<T,MT> Srl, const T *p)
friend d_Boolean
operator==(const T *p, const d_Rel_Ref<T> Sr2)
friend d_Boolean
operator==(const d_Rel_Ref<T,MT> Srl, const d_Ref_Any Sr2)
friend d_Boolean
operator==(const d_Ref_Any Srl, const d_Rel_Ref<T,MT> Sr2)
Эти булевы операторы равенства возвращают истинное значение, если сравниваемые ссылки либо указывают на один и тот же экземпляр, либо являются нулевыми. В противном случае они возвращают ложное значение.
friend d_Boolean
operator I = (const d_Rel_Ref<T,MT> Srl, const d_Rel_Ref<T,MT> Sr2)
Этот булев оператор неравенства возвращает истинное значение, если сравниваемые ссылки не указывают на один и тот же экземпляр. Если же ссылки указывают на один и тот же экземпляр или являются нулевыми, он возвращает ложное значение.
friend d_Boolean
operator 1=(const d_Rel_Ref<T,MT> Srl, const T *p)
friend d_Boolean
operator!=(const T *p, const d_Rel_Ref<T,MT> Sr2)
friend d_Boolean
operator!=(const d_Rel_Ref<T,MT> Srl, const d_Ref_Any Sr2)
friend d_Boolean-••
operator!=(const d_Ref_Any Srl, const d_Rel_Ref<T,MT> Sr2)
Эти булевы операторы неравенства возвращают истинное значение, если сравниваемые ссылки не указывают на один и тот же экземпляр. В противном случае они возвращают ложное значение.-■
template <class Т, const{public:template <class Т, const{public:d_Rel_Set<T,MT> &d_Rel_Set<T,MT> &d_Rel_Set<T,MT> &d_Rel_Set<T,MT> s d_Set<d_Ref<T> > friend d_Set<d_Ref<T> >d_Rel_Set<T,MT> &d_Rel_Set<T,MT> & d_Rel_Set<T,MT> & d_Set<d_Ref<T> >friend d_Set<d_Ref<T> >d_Rel_Set<T,MT> sd_Rel_set<T,MT> s d_Rel_Set<T,MT> s d_Set<d_Ref<T> >friend d Set<d Ref<T> >d_Rel_Set<T,MT> &d_Rel_Set<T,MT> &d_Rel_Set<T,MT> &d_Rel_Set<T,MT> s d_Set<d_Ref<T> > friend d_Set<d_Ref<T> >d_Rel_Set<T,MT> &d_Rel_Set<T,MT> & d_Rel_Set<T,MT> & d_Set<d_Ref<T> >friend d_Set<d_Ref<T> >d_Rel_Set<T,MT> sd_Rel_set<T,MT> s d_Rel_Set<T,MT> s d_Set<d_Ref<T> >friend d Set<d Ref<T> >
template <class Т, const
{
public:
template <class Т, const
{
public:
d_Rel_Set<T,MT> &
d_Rel_Set<T,MT> &
d_Rel_Set<T,MT> &
d_Rel_Set<T,MT> s d_Set<d_Ref<T> > friend d_Set<d_Ref<T> >
d_Rel_Set<T,MT> &
d_Rel_Set<T,MT> & d_Rel_Set<T,MT> & d_Set<d_Ref<T> >
friend d_Set<d_Ref<T> >
d_Rel_Set<T,MT> s
d_Rel_set<T,MT> s d_Rel_Set<T,MT> s d_Set<d_Ref<T> >
friend d Set<d Ref<T> >
d_Rel_Set<T,MT> &
d_Rel_Set<T,MT> &
d_Rel_Set<T,MT> &
d_Rel_Set<T,MT> s d_Set<d_Ref<T> > friend d_Set<d_Ref<T> >
d_Rel_Set<T,MT> &
d_Rel_Set<T,MT> & d_Rel_Set<T,MT> & d_Set<d_Ref<T> >
friend d_Set<d_Ref<T> >
d_Rel_Set<T,MT> s
d_Rel_set<T,MT> s d_Rel_Set<T,MT> s d_Set<d_Ref<T> >
friend d Set<d Ref<T> >
Этот шаблонный класс представляет неупорядоченную двунаправленную связь типа “ко-многим” между классами Зит. Экземпляр класса d_Rel_Set<T, МТ> встроен в класс 3. Перманентный класс т на другом конце двунаправленной связи должен иметь член типа d_Rel_List<3, MS>, d_Rel_Set<3, М3> или d_Rel_Ref<3, мз>. Второй шаблонный параметр мт должен быть строковой переменной, содержащей имя члена класса т, который ссылается на класс 3. Аналогично этот член в классе т должен иметь второй шаблонный параметр, который ссылается на соответствующий член в классе s.
Пример заголовочного файла.
extern const char _refT[], _setS[];
class S {
d_Rel_Set<T,_refS> setT;
class T { d_Rel_X<S,_setT> refS; // X может быть Ref, List или Set } ;
Пример программного файла.
const char _refS[] = "refS";
const char _setT[] = "setT";
Этот класс наследует свой интерфейс от базового класса d_Set<d_Ref<T>>.
Синопсис
char *MT> class d_Rel_Set :public d_Set<d_Ref<T> >
d_Rel_Set();
d_Rel_Set(const d_Set<d_Ref<T> > &s2); ~d_Rel_Set() ;
operator=(const d_Set<d_Ref<T> > &s2); union_of(const d_Set<d_Ref<T> > &sl, const d_Set<d_Ref<T> > &s2);
union_with(const d_Set<d_Ref<T> > &s2); operator+=(const d_Set<d_Ref<T> > &s2); create_union(const d_Set<d_Ref<T> >&s2)const; operator!(const d_Set<d_Ref<T> > &sl, const d_Set<d_Ref<T> > &s2);
intersection_of(const d_Set<d_Ref<T> > &sl, const d_Set<d_Ref<T> > &s2);
intersection_with(const d_Set<d_Ref<T> >&s2); operator*(const d_Set<d_Ref<T> > &s2); create_intersection(
const d_Set<d_Ref<T> > &s2) const; operator*(const d_Set<d_Ref<T> > &sl, const d_Set<d_Ref<T> > &s2); difference_of(const d_Set<d_Ref<T> > &sl, const d_Set<d_Ref<T> > &s2); difference_with(const d_Set<d_Ref<T> > Ss2); operator-=(const d_Set<d_Ref<T> >&s2) ; create_difference(
const d_Set<d_Ref<T> > &s2) const; operator-(const d_Set<d_Ref<T> > &sl,
d_Boolean d_Booleand_Booleand_Booleand_Boolean d_Booleand_Booleand_Booleanconst d_Set<d_Ref<T> > &s2);
d_Boolean d_Boolean
d_Boolean
d_Boolean
d_Boolean d_Boolean
d_Boolean
d_Boolean
is_subset_of(const d_Set<d_Ref<T> >&s2) const; is_proper_subset_of(
const d_Set<d_Ref<T> > &s2) const; is_superset_of(
const d_Set<d_Ref<T> > &s2) const; is_proper_superset_of(
const d_Set<d_Ref<T> > &s2) const;
Описание
d_Rel_Set ()
Нулевой конструктор инициализирует объект множества без элементов.
d_Rel_Set(const d_Set<d_Ref<T> > &s2)
Конструктор копии инициализирует объект таким образом, чтобы он содержал те же элементы, что и объект s2. Для каждого элемента, входящего в состав объекта s2, член в адресуемом экземпляре класса т, заданный параметром МТ, модифицируется для добавления ссылки на экземпляр класса S, содержащий этот объект.
~d_Rel_Set()
Деструктор отменяет инициализацию объекта, удаляя все элементы из множества. Для каждого элемента этого множества член в адресуемом экземпляре класса т, заданный параметром МТ, модифицируется для удаления ссылки на экземпляр класса S, содержащий этот объект.
d_Rel_Set<T, МТ> &
operator=(const d_Set<d_Ref<T> > &s2)
Этот оператор используется для присвоения содержимого одного множества другому. Если объект до выполнения этой операции имел какие-либо элементы, они сначала будут удалены. Для каждого удаленного элемента член в адресуемом экземпляре класса т, заданный параметром мт, модифицируется для удаления ссылки на экземпляр класса S, содержащий этот объект.
Этот объект будет содержать копию элементов множества, заданного параметром s2. Для каждого элемента множества s2, который копируется в объект, член в адресуемом экземпляре класса т, заданный параметром мт, модифицируется для добавления ссылки на экземпляр класса S, содержащий этот объект. Функция возвращает ссылку на объект, разрешая каскадное выполнение операции присваивания.
d_Rel_Set<T, МТ> &
union_of(const d_Set<d_Ref<T> > &sl, const d_Set<d_Ref<T> > &s2)
Функция создает и помещает в данный объект объединение множеств si и s2. При этом исходные элементы в объекте удаляются. Для каждого удаленного элемента член в адресуемом экземпляре класса т, заданный параметром мт, модифицируется для удаления ссылки на экземпляр класса S, содержащий этот объект. Для каждого элемента, который входит в созданное объединение, член в адресуемом экземпляре класса т, заданный параметром мт, модифицируется для добавления ссылки на экземпляр класса S, содержащий этот объект. Ниже приводится пример синтаксиса.
aset.union_of(setl, set2);
Функция возвращает ссылку на этот объект.
d_Rel_Set<T, MT> &
union_with(const d_Set<d_Ref<T> > &s2)
Функция создает объединение данного множества и множества s2. Результат объединения размещается в исходном объекте. Для каждого элемента, добавленного из множества s2, член в адресуемом экземпляре класса т, заданный параметром МТ, модифицируется для добавления ссылки на экземпляр класса S, содержащий этот объект. Ниже приводится пример синтаксиса.
aset.union_with(set2);
Функция возвращает ссылку на этот объект.
d_Rel_Set<T, МТ> s
operator+=(const d_Set<d_Ref<Т> > &s2)
Эта функция выполняет то же действие, что и предыдущая, но для этого использует оператор operator+=. Объединение элементов множеств данного объекта и множества з2 размешается в исходном объекте. Для каждого элемента, добавленного из множества s2, член в адресуемом экземпляре класса т, заданный параметром МТ, модифицируется для добавления ссылки на экземпляр класса S, содержащий этот объект. Ниже приводится пример синтаксиса.
aset += set2;
Функция возвращает ссылку на этот объект.
d_Set<d_Ref<Т> >
create_union(const d_Set<d_Ref<T> > Ss2) const
Функция создает объединение данного множества и множества s2. Ниже приводится пример синтаксиса.
setresult = set.create_union(set2);
Исходный объект не изменяется, а результат представляет собой новый объект множества, возвращаемый по значению. Поскольку результатом этой операции является новый автономный объект множества, никакой обработки двунаправленной связи не выполняется.
friend d_Set<d_Ref<T> >
operator*(const d_Set<d_Ref<T> > &sl, const d_Set<d_Ref<T> > &s2)
Функция создает объединение множеств si и s2, которое возвращается по значению. Поскольку результатом этой операции является новый автономный объект множества, никакой обработки двунаправленной связи не выполняется. Ниже приводится пример синтаксиса.
setl + set2
Результатом является множество, возвращаемое по значению.
d_Rel_Set<T, МТ> & intersection_of(const d_Set<d_Ref<T> > &sl, const d_Set<d_Ref<T> > &s2)
Функция создает и помещает в данный объект пересечение множеств si и s2. При этом исходные элементы в объекте удаляются. Для каждого удаленного элемента член в адресуемом экземпляре класса Т, заданный параметром МТ, модифицируется для удаления ссылки на экземпляр класса S, содержащий этот объект. Для каждого элемента, который входит в состав пересечения, член в адресуемом экземпляре класса Т, заданный параметром МТ, модифицируется для включения ссылки на экземпляр класса S, содержащий этот объект. Ниже приводится пример синтаксиса.'
aset.intersection_of(setl, set2);" ':
Функция возвращает ссылку на данный объект.,
d_Rel_Set<T, MT> &
intersection_with(const d_Set<d_Ref<T> > &s2)
Функция создает пересечение данного множества и множества s2. Результат пересечения размешается в исходном объекте. Поскольку пересечение содержит элементы обоих множеств, никаких новых элементов в данный объект добавлено не будет. Однако при этом из исходного объекта должны быть удалены все элементы, которых нет в множестве s2. Поэтому для каждого элемента, удаленного из объекта, член в адресуемом экземпляре класса т, заданный параметром МТ, модифицируется для удаления ссылки на экземпляр класса S, содержащий этот объект. Ниже приводится пример синтаксиса.
aset.intersection_with(set2) ;
Функция возвращает ссылку на данный объект.
d_Rel_Set<T, МТ> 5
operator*=(const d_Set<d_Ref<T> > &s2)
Эта функция выполняет ту же операцию, что и функция incersection_with. Ниже приводится пример синтаксиса.
aset *= set2;
Функция возвращает ссылку на данный объект.
d_Set<d_Ref<T> >
create_intersection(const d_Set<d_Ref<T> > &s2) const
Функция создает пересечение множества данного объекта и множества s2. Ниже приводится пример синтаксиса.
setresult = set.create_intersection(set2);
Исходный объект не изменяется, а результат представляет собой новый объект множества, возвращаемый по значению. Поскольку результатом является новый объект типа d_Set<d_Ref<T>>, возвращаемый по значению, никакой обработки двунаправленной связи не требуется.
friend d_Set<d_Ref<T> >
operator* (const d_Set<d_Ref<T> > &sl, const d_Set<d_Ref<T> > &s2);
Функция создает пересечение множеств si и s2.
Ниже приводится пример синтаксиса.
setl * set2
Поскольку результатом является множество, возвращаемое по значению, никакой обработки двунаправленной связи не требуется.
d_Rel_Set<T, МТ> &
difference_of(const d_Set<d_Ref<T> > &sl, const d_Set<d_Ref<T> > &s2)
Функция создает и помещает в данный объект разность множеств si и s2 (si - s2). При этом исходные элементы в объекте удаляются. Для каждого удаленного элемента член в адресуемом экземпляре класса т, заданный параметром мт, модифицируется для удаления ссылки на экземпляр класса S, содержащий этот объект. Ниже приводится пример синтаксиса.
aset.difference_of(setl, set2);
Функция возвращает ссылку на данный объект.
d_Rel_Set<T, МТ> &
difference_with(const d_Set<d_Ref<T> > &s2)
Функция вычисляет разность данного множества и множества s2. Результат размещается в исходном объекте. При вычислении разности каждый элемент данного объекта, значение которого содержится в множестве s2, удаляется из
этого объекта множества. Поэтому для каждого элемента, удаленного из объекта, член в адресуемом экземпляре класса т, заданный параметром мт, модифицируется для удаления ссылки на экземпляр класса S, содержащий этот объект. Ниже приводится пример синтаксиса.
aset.difference_with(set2); // (aset - set2)
Функция возвращает ссылку на данный объект.
d_Rel_Set<T, mt> &
operator-=(const d_Set<d_Ref<T> > &s2)
Эта функция выполняет ту же операцию, что и функция difference_with, т.е. из объекта вычитается множество s2, а результат помещается в исходный объект. При вычислении разности каждый элемент данного объекта, значение которого содержится в множестве s2, удаляется из этого объекта множества. Поэтому для каждого элемента, удаленного из объекта, член в адресуемом экземпляре класса Т, заданный параметром МТ, модифицируется для удаления ссылки на экземпляр класса s, содержащий этот объект. Ниже приводится пример синтаксиса.
set -= set2;
Функция возвращает ссылку на данный объект.
d_Set<d_Ref<Т> >
create_difference(const d_Set<d_Ref<T> > &s2) const
Создается разность данного объекта и множества s2.
Ниже приводится пример синтаксиса.
setresult = aset.create_difference(set2);
Искомый объект не изменяется, а результат в виде нового объекта множества возвращается по значению. Поскольку новый объект множества возвращается по значению, никакой обработки двунаправленной связи не требуется.
friend d_Set<d_Ref<Т> >
operator-(const d_Set<d_Ref<Т> > &setl, const d_Set<d_Ref<T> > &set2)
Вычисляется разность множеств si и s2, а результат возвращается по значению. Поскольку новый объект множества возвращается по значению, никакой обработки двунаправленной связи не требуется. Ниже приводится пример синтаксиса.
setl - set2
d_Boolean
is_subset_of(const d_Set<d_Ref<T> > &s2) const
Если данное множество является подмножеством множества s2, эта булева функция возвращает истинное значение, в противном случае — ложное.
d_Boolean
is_proper_subset_of(const d_Set<d_Ref<T> > &s2) const
Если данное множество является собственным подмножеством множества s2, эта булева функция возвращает истинное значение, в противном случае — ложное.
d_Boolean
is_superset_of(const d_Set<d_Ref<T> > &s2) const
Если данное множество является супермножеством множества s2, эта булева функция возвращает истинное значение, в противном случае — ложное.
d_Boolean
is_proper_superset_of(const d_Set<d_Ref<Т> > &s2) const
Если данное множество является собственным супермножеством множества s2, эта булева функция возвращает истинное значение, в противном случае — ложное.
Неупорядоченная коллекция (множество) элементов типа т, не допускающая повторяющихся элементов. Определенные на множествах операции объединения, пересечения и вычитания описаны в разделе 7.4.2.
Синопсис
template <class Т> class d_Set : public d_Collection<T> (
public:
d_Set();
d_Set(const d_Set<T> &s2);
~d_Set();
d_Set<T> & d_Set<T> &
operator”(const d_Set<T> &s2); union_of(const d_Set<T> &sl, const d_Set<T> &s2);
d_Set<T> & d_Set<T> & d_Set<T> friend d_Set<T>
union_with(const d_Set<T> &s2);
operator+=(const d_Set<T> &s2);
create_union(const d_Set<T> ss2) const; operator+(const d_Set<T> &sl, const d_Set<T> &s2);
d_Set<T> s
intersection_of(const d_Set<T> &sl, const d_Set<T> Ss2);
d_Set<T> & d_Set<T> s d_Set<T> friend d_Set<T>
intersection_with(const d_Set<T> &s2);
operator*”(const d_Set<T> &s2);
create_intersection(const d_Set<T> &s2) const; operator*(const d_Set<T> &sl,
const d_Set<T> Ss2);
d_Set<T> &
difference_of(const d_Set<T> &sl, const d Set<T> &s2);
d_Set<T> & d_Set<T> & d_Set<T> friend d_Set<T>
difference_with(const d_Set<T> &s2);
operator-™(const d_Set<T> &s2);
create_difference(const d_Set<T> &sl) const; operator-(const d_Set<T> &sl,
const d_Set<T> &s2);
d_ Boolean d_Boolean d_Boolean d_Boolean
is_subset_of(const d_Set<T> &s2) const;
is_proper_subset_of(const d_Set<T> &s2) const; is_superset_of(const d_Set<T> Ss2) const;
is_proper_superset_of(
const d_Set<T> ss2)const;
Описание
d_Set()
Нулевой конструктор инициализирует объект множества без элементов.
d_Set(const d_Set<T> &s2)
Конструктор копии инициализирует объект, который будет содержать те же элементы, что и объект s2.
~d_Set()
Деструктор отменяет инициализацию объекта, удаляя все элементы из данного множества. Если класс элементов имеет деструктор, он вызывается для каждого элемента.
d_Set<T> &
operator=(const d_Set<T> &s2)
Этот оператор используется для присвоения одному множеству содержимого другого множества. Он возвращает ссылку на объект, позволяя каскадное присваивание. Если до этого объект имел какие-либо элементы, они удаляются. В результате объект будет содержать копию элементов множества s2.
d_Set<T> &
union_of(const d_Set<T> &sl, const d_Set<T> &s2)
Функция создает объединение множеств si и s2, помещая результат в данный объект. Исходные элементы данного объекта множества удаляются до выполнения операции. Ниже приводится пример синтаксиса.
aset.union_of(setl, set2);
Функция возвращает ссылку на данный объект.
d_Set<T> &
union_with(const d_Set<T> &s2)
Функция создает объединение данного множества и множества s2. Результат объединения помещается в исходный объект. Ниже приводится пример синтаксиса.
aset.union_with(set2), ;
Функция возвращает ссылку на данный объект.
d_Set<T> &
operator+=(const d_Set<T> &s2)
Эта функция для операции, которую выполняет функция union_with, использует оператор operator+=. Результат объединения данного объекта и множества s 2 помешается в исходный объект. Функция возвращает ссылку на этот объект. Ниже приводится пример синтаксиса.
aset += set2;
d_Set<T>
create_union(const d_Set<T> &s2) const
Функция создает объединение данного множества и множества s2. Ниже приводится пример синтаксиса.
setresult = set.create_union(set2) ;
Объект исходного множества не изменяется, а результатом является новый объект множества, возвращаемый по значению.
friend d_Set<T>
operator*(const d_Set<T> &sl, const d_Set<T> &s2)
Функция создает объединение множеств si и s2, а множество результата возвращается по значению. Ниже приводится пример синтаксиса.
setresult = setl + set2
d_Set<T> &
intersection_of(const d_Set<T> &sl, const d_Set<T> &s2)
Функция создает пересечение множеств si и s2, помещая результат в данный объект. Исходные элементы данного объекта множества удаляются до выполнения операции. Ниже приводится пример синтаксиса.
aset.intersection_of(setl, set2);'
Функция возвращает ссылку на данный объект.
d_Set<T> &
intersection_with(const d_Set<T> &s2)
Функция создает пересечение данного множества и множества s2. Результат пересечения помешается в исходный объект. Ниже приводится пример синтаксиса.
aset.intersection_with(set2);
Функция возвращает ссылку на данный объект.
d_Set<T> &
operator*”(const d_Set<T> &s2)
Эта функция для операции, которую выполняет функция intersection_with, использует оператор operator*”. Результат пересечения данного объекта и множества s2 помещается в исходный объект. Функция возвращает ссылку на этот объект. Ниже приводится пример синтаксиса.
aset *= set2;
d_Set<T>
create_intersection(const d_Set<T> &s2) const
Функция создает пересечение данного множества и множества з2. Ниже приводится пример синтаксиса.
setresult = set.create_intersection(set2);
Объект исходного множества не изменяется, а результатом является новый объект множества, возвращаемый по значению.
friend d_Set<T>
operator*(const d_Set<T> &sl, const d_Set<T> &s2)
Функция создает пересечение множеств si и s2. Ниже приводится пример синтаксиса.
setresult = setl * set2
Результат представляет собой новый объект класса d_Set<T>, возвращаемый по значению.
d_Set<T> &
difference_of(const d_Set<T> &sl, const d_Set<T> &s2)
Функция создает разность множеств si и s2 (si - s2), помещая результат в данный объект. Исходные элементы, содержащиеся в данном объекте множества, удаляются до выполнения операции. Ниже приводится пример синтаксиса.
aset.difference_of(setl, set2);
Функция возвращает ссылку на данный объект.
d_Set<T> &
difference_with(const d_Set<T> &s2)
Функция создает разность данного множества и множества s2. Результат вычитания помещается в исходный объект. Ниже приводится пример синтаксиса.
aset.difference_with(set2) ;
Функция возвращает ссылку на данный объект.
d_Set<T> &
operator-”(const d_Set<T> &s2)
Эта функция для операции, которую выполняет функция difference_with, использует оператор operator-”. Результат вычитания множества s2 из дан-
ного объекта помещается в исходный объект. Функция возвращает ссылку на этот объект. Ниже приводится пример синтаксиса.-
aset -= set2;
d_Set<T>
create_difference(const d_Set<T> &s2) const
Функция создает разность данного множества и множества s2. Ниже приводится пример синтаксиса.
setresult = aset.create_difference(set2);
Объект исходного множества не изменяется, а результатом является новый объект множества, возвращаемый по значению.
friend d_Set<T>
operator-(const d_Set<T> &sl, const d_Set<T> &s2)
Функция создает разность множеств si и s2. Ниже приводится пример синтаксиса, setresult = setl - set2
Результат представляет собой новый объект класса d_Set<T>, возвращаемый по значению.
d_Boolean
is_subset_of(const d_Set<T> &s2) const
Если данное множество является подмножеством множества s2, эта булева функция возвращает истинное значение, в противном случае — ложное (см. раздел 7.4.1).
d_Boolean
is_proper_subset_of(const d_Set<T> &s2) const
Если данное множество является собственным подмножеством множества s2, эта булева функция возвращает истинное значение, в противном случае — ложное (см. раздел 7.4.1).
d_Boolean
is_superset_of(const d_Set<T> &s2) const
Если данное множество является супермножеством множества s2, эта булева функция возвращает истинное значение, в противном случае — ложное (см. раздел 7.4.1).
d_Boolean
is_proper_superset_of(const d_Set<T> &s2) const
Если данное множество является собственным супермножеством множества s2, эта булева функция возвращает истинное значение, в противном случае — ложное (см. раздел 7.4.1).
class d_String { public:class d_String { public:d_String s d_String s char s unsigned long friend d_Boolean friend d_Boolean friend d_Boolean friend d_Boolean friend d_Boolean friend d_Boolean friend d_Boolean friend d_Boolean friend d_Boolean friend d_Boolean friend d_Boolean friend d_Boolean friend d_Boolean friend d_Boolean friend d_Boolean friend d_Boolean friend d_Boolean friend d_Boolean );d_String s d_String s char s unsigned long friend d_Boolean friend d_Boolean friend d_Boolean friend d_Boolean friend d_Boolean friend d_Boolean friend d_Boolean friend d_Boolean friend d_Boolean friend d_Boolean friend d_Boolean friend d_Boolean friend d_Boolean friend d_Boolean friend d_Boolean friend d_Boolean friend d_Boolean friend d_Boolean );
class d_String { public:
class d_String { public:
d_String s d_String s char s unsigned long friend d_Boolean friend d_Boolean friend d_Boolean friend d_Boolean friend d_Boolean friend d_Boolean friend d_Boolean friend d_Boolean friend d_Boolean friend d_Boolean friend d_Boolean friend d_Boolean friend d_Boolean friend d_Boolean friend d_Boolean friend d_Boolean friend d_Boolean friend d_Boolean );
d_String s d_String s char s unsigned long friend d_Boolean friend d_Boolean friend d_Boolean friend d_Boolean friend d_Boolean friend d_Boolean friend d_Boolean friend d_Boolean friend d_Boolean friend d_Boolean friend d_Boolean friend d_Boolean friend d_Boolean friend d_Boolean friend d_Boolean friend d_Boolean friend d_Boolean friend d_Boolean );
Класс, используемый для представления строки переменной длины.
Синопсис
d_String();
d_String(const d_String Ss2);
d_String(const char *s2);
~d_String();
operator™(const d_String &s2);
operator™(const char *s2);
operator const char *() const; operator[](unsigned long index); length() const;
operator™™(const d_String Ssl, const d_String Ss2);
operator™™(const d_String Ssl, const char *s2);
operator™™(const char *sl,
const d_String Ss2); operator!=(const d_String Ssl,
const d_String &s2);
operator!=(const d_String Ssl, const char *s2);
operator!=(const char *sl, const d_String &s2);
operator<(const d_String Ssl, const d_String Ss2);
operator<(const d_String ssl, const char *s2);
operator<(const char *sl,
const d_String ss2);
operator<=(const d_String Ssl, const d_String &s2);
operator<=(const d_String &sl, const char *s2);
operator<=(const char *sl,
const d_String &s2); operator>(const d_String &sl,
const d_String &s2); operator>(const d_String ssl, const char *s2);
operator>(const char ‘si,
const d_String Ss2); operator>=(const d_String Ssl,
const d_String ss2); operator>=(const d_String Ssl, const char *s2);
operatoo™ (const char *sl, const d_String ss2);
Оппсание
d_String()
Нулевой конструктор инициализирует объект нулевым значением строки.
d_String(const d_String &s2)
Конструктор копии инициализирует объект значением строки з2.
d_String(const char *s2)
Этот конструктор инициализирует объект значением строки, адресуемой указателем s2.
~d_St ring()
Деструктор отменяет инициализацию строки.
d_String &
operator=(const d_String &s2)
Этот оператор присваивает объекту копию строки s2 и возвращает ссылку на этот объект.
d_String &
operator=(const char *s2)
Оператор присвоения присваивает объекту копию строкового значения, адресуемого указателем s2, и возвращает ссылку на этот объект.
operator const char *() const
Функция преобразует объект типа d_string в символьный массив с завершающим null-символом. Ответственность за освобождение возвращаемого значения несет объект типа d_String.
char &
operator!](unsigned long index)
Эта функция предоставляет доступ к символу строки, расположенному в позиции index. Первый символ расположен в позиции 0.
unsigned long length() const
Функция возвращает длину строки.
friend d_Boolean
operator==(const d_String &sl, const d_String &s2)
friend d_Boolean
operator==(const d_String &sl, const char *s2)
friend d_Boolean
operator==(const char *sl, const d_String &s2)
Если строки, адресуемые параметрами si и s2, равны, эти булевы функции возвращают истинное значение, в противном случае — ложное.
friend d_Boolean
operator 1=(const d_String &sl, const d_String &s2)
friend d_Boolean
operator!=(const d_String &sl, const char *s2)
friend d_Boolean
operator!=(const char *sl, const d_String &s2)
Если строки, адресуемые параметрами si и s2, не равны, эти булевы функции возвращают истинное значение, в противном случае (если строки равны) — ложное.'
friend d_Boolean
operator<(const d_String &sl, const d_String &s2)
friend d_Boolean
operator<(const d_String &sl, const char *s2)
friend d_Boolean
operator<(const char *sl, const d_String &s2)
Если строка, адресуемая параметром si, лексически меньше строки, адресуемой параметром s2, эти булевы функции возвращают истинное значение, в противном случае — ложное.
friend d_Boolean
operator<=(const d_String &sl, const d_String &s2)
friend d_Boolean
operators(const d_String &sl, const char *s2)
friend d_Boolean
operator<=(const char *sl, const d_String &s2)
Если строка, адресуемая параметром si, лексически меньше или равна строке, адресуемой параметром s2, эти булевы функции возвращают истинное значение, в противном случае — ложное.
friend d_Boolean
operator>(const d_String &sl, const d_String &s2)
friend d_Boolean
operator>(const d_String &sl, const char *s2)
friend d_Boolean
operator>(const char *sl, const d_String &s2)
Если строка, адресуемая параметром si, лексически больше строки, адресуемой параметром s2, эти булевы функции возвращают истинное значение, в противном случае — ложное.
friend d_Boolean
operator>=(const d_String &sl, const d_String &s2)
friend d_Boolean
operator>=(const d_String &sl, const char *s2)
friend d_Boolean
operator>=(const char *sl, const d_String &s2)
Если строка, адресуемая параметром si, лексически больше или равна строке, адресуемой параметром s2, эти булевы функции возвращают истинное значение, в противном случае — ложное.
Класс, используемый для представления конкретного значения времени.
Синопсис
class d_Time {
public:
enum Time Zone {
GMT
=
0, GMT12 = 12
, GMT_12
=
-12
, GMT1
GMT_
1
= -1,
GMT 2 =
2, GMT_2
=
-2,
GMT3
GMT_
"3
= -3,
GMT 4 =
4, GMT_4
=
-4,
GMT 5 '
GMT_
"5
= -5,
GMT 6 =
6, GMT_6
=
"6,
GMT 7
GMT^
'7
= -7,
GMT 8 =
8, GMT_8
=
-8,
GMT 9
GMT
"9
= -9,
GMT10 =
10, GMT
10
=-
-io.
GMT 11
= 11,
GMT 11
= -11, USeastern = -
UScentral = -6, USmountain = -7, USpacific = -8 };
static voidset_default_Time_Zone(Time_Zone tz) ;
static voidset_default_Time_Zone_to_local();
d_Time();
d_Time(unsigned short hour, unsigned short minute, float sec = O.Of);
d_Time(unsigned short hour, unsigned short minute, float sec, short tzhour, short tzminute);
d_Time(const d_Time St);
d_Time (const d_Timestamp Sts);
d_Time soperator=(constd_Timest);
d_Time &operator=(constd_Timestamp &ts);
unsigned shorthour!) const;
unsigned shortminuted const;
floatsecond() const;
Time_Zone short short static d_Time d_Time & d_Time & friend d_Time
friend d_Time
friend d_Interval friend d_Time
friend d_Boolean
friend d_Boolean
friend d_Boolean friend d_Boolean
friend d_Boolean friend d_Boolean
friend d_Boolean
332
time_zone() const;
tz_hour() const;
tz_minute() const;
current();
operator+=(const d_Interval &i);
operator-=(const d_Interval &i);
operator+(const d_Time &t, const d_Interval &i);
operator+(const d_Interval &t, const d_Time &i);
operator-(const d_Time &t, const d_Time St);
operator-(const d_Time st, const d_Interval si);
operator==(const d_Time &tl, const d_Time St2);
operator!=(const d_Time &tl, const d_Time &t2);
operator<(const d_Time &tl, const d_Time &t2); operator<=(const d_Time &tl, const d_Time &t2);
operator>(const d_Time &tl, const d_Time &t2); operator>=(const d_Time stl, const d_Time &t2);.
overlaps(const d_Time SsL, const d_Time seL, const d_Time &sR,const d_Time seR) ;
, Часть V; Приложение
friend d_Booleanoverlaps(const d_Timestamp &sL,
const d_Timestamp &eL, const d_Time SsR, const d_Time &eR);
friend d_Booleanoverlaps( const d_Time &sL, const d_Time &eL,
const d_Timestamp &sR, const d_Timestamp &eR) ;
) ;
Описание
enum Time_2one
Это перечисление представляет значения часовых поясов.
static void
set_default_Time_2one(Time_Zone)
Функция изменяет действующее по умолчанию значение часового пояса.
static void
set_default_Time_Zone_to_local()
Функция устанавливает значение местного часового пояса для использования в среде приложения клиента.
d_Time()
Этот конструктор инициализирует объект значением текущего времени.
d_Time(unsigned short hour, unsigned short minute, float sec = O.Of)
Этот конструктор инициализирует объект заданными значениями часов, минут и секунд для установленного по умолчанию часового пояса. Действующее по умолчанию значение для параметра sec добавлено в ODMG 2.0.
d_Time(unsigned short hour, unsigned short minute, float sec, short tzhour, short tzminute)
Этот конструктор инициализирует объект заданными значениями часов, минут и секунд. Параметры tzhour и tzminute означают число часов и минут по Гринвичу (Greenwich Mean Time — GMT).
d_Time(const d_Time st)
Конструктор копии инициализирует объект значением времени, заданным параметром t.
d_Time(const d_Timestamp Sts)
Этот конструктор копии инициализирует объект значениями компонентов времени, содержащимися в заданном параметре ts.
d_Time &
operator=(const d_Time St)
Этот оператор присваивает объекту значение времени, заданное параметром t, и возвращает ссылку на этот объект.
d_Time &
operator=(const d_Timestamp Sts)
Этот оператор присваивает объекту значения компонентов времени, содержащиеся в заданном параметре ts, и возвращает ссылку на этот объект.
unsigned short.
hour() const.
Функция возвращает количество часов.
unsigned short minute() const
Функция возвращает количество минут.
float
secondO const
Функция возвращает количество секунд.
Time_Zone
time_zone() const
Функция возвращает значение часового пояса. Эта функция добавлена в ODMG 2.0.
short
tz_hour() const
Функция возвращает количество часов по Гринвичу (GMT).
short
tz_minute() const
Функция возвращает количество минут по Гринвичу (GMT).
static d_Time
current()
Функция возвращает текущее время.
d_Time &
operator+=(const d_Interval &i)
Функция перемещает время объекта вперед на интервал, заданный параметром i, и возвращает ссылку на этот объект.
d_T ime & '
operator-=(const d_Interval Si)
Функция вычитает из объекта типа d Time интервал, заданный значением параметра i, и возвращает ссылку на этот объект.
friend d_Time
operator+(const d_Time &t, const d_Interval Si)
friend d_Time
operator+(const d_Interval Si, const d_Time St)
Функция суммирует объекты времени и интервала, заданные параметрами t и i, а новый объект типа d_Time возвращает по значению.
friend d_Interval
operator-(const d_Time Stl, const d_Time St2)
Функция вычисляет разность значений времени tl и t2 (tl - t2) и возвращает результат как объект интервала (типа d_interval).
friend d_Time
operator-(const d_Time St, const d_Interval Si)
Функция вычитает из значения времени значение интервала и возвращает новое значение времени типа d_Time.
friend d_Boolean
operator==(const d_Time Stl, const d_Time St2)
Если заданные объекты времени tl и t2 равны, эта булева функция возвращает истинное значение, в противном случае — ложное.
friend d_Boolean
operator!=(const d_Time &tl, const d_Time &t2)
Если заданные объекты времени tl и t2 не равны, эта булева функция возвращает истинное значение. Если они равны, функция возвращает ложное значение.
friend d_Boolean
operator<(const d_Time &tl, const d_Time &t2)
Если время tl меньше t2, эта булева функция возвращает истинное значение, в противном случае — ложное.
friend d_Boolean
operator<=(const d_Time &tl, const d_Time &t2)
Если время tl меньше или равно t2, эта булева функция возвращает истинное значение, в противном случае — ложное.
friend d_Boolean
operator>(const d_Time &tl, const d_Time &t2)
Если время tl больше t2, эта булева функция возвращает истинное значение, в противном случае — ложное.
friend d_Boolean
operator>=(const d_Time &tl, const d_Time st2)
Если время tl больше или равно t2, эта булева функция возвращает истинное значение, в противном случае — ложное.
friend d_Boolean
overlaps(const d_Time &sL, const d_Time seL, const d_Time &sR, const d_Time &eR)
friend d_Boolean
overlaps(const d_Timestamp SsL, const d_Timestamp &eL, const d_Time SsR, const d_Time &eR)
friend d_Boolean
overlaps(const d_Time &sL, const d_Time SeL, const d_Timestamp &sR, const d_Timestamp &eR)
Эти булевы функции-друзья возвращают истинное значение, если в периоде времени L (ограниченном значениями времени sL и eL) и в периоде времени R (ограниченном значениями времени sR и eR) есть какая-либо общая составляющая. В противном случае они возвращают ложное значение.
Класс, используемый для представления в одном объекте как даты, так и времени.
Синопсис
class d_Timestamp { public:
d_Timestamp & d_Timestamp & const d_Date t const d_Date & unsigned short unsigned short unsigned short unsigned short unsigned short float short short
static d_Timestamp d_Timestamp t d_Timestamp &
friend d_Timestamp friend d_Timestamp friend d_Interval friend d_Timestamp friend d_Boolean friend d_Boolean friend d_Boolean friend d_Boolean friend d_Boolean friend d_Boolean
friend d Boolean
friend d_Boolean
friend d Boolean
d_Timestamp();
d_Timestamp(unsigned short year,
unsigned short month = 1, unsigned short day = 1, unsigned short hour = 0, unsigned short minute = 0, float sec = 0 . Of);
d_Timestamp(const d_Date id) ;
d_Timestamp(const d_Date id, const d_Time it); d_Timestamp(const d_Timestamp its);
operator=(const d_Timestamp its);
operator=(const d_Date id);
date() const;
timet) const;
year() const;
month() const;
day() const;
hour() const;
minute() const;
second() const;
tz_hour() const;
tz_minute() const;
current();
operator+=(const d_Interval ii);
operator-=(const d_Interval ii);
operator+(const d_Timestamp its, const d_Interval ii);
operatort(const d_Interval ii, const d_Timestamp its);
operator-(const d_Timestamp itsl, const d_Timestamp its2);
operator-(const d_Timestamp its, const d_Interval ii) ;
operator==(const d_Timestamp itsl, const d_Timestamp its2);
operator I=(const d_Timestamp itsl, const d_Timestamp its2);
operator<(const d_Timestamp itsl, const d_Timestamp its2);
operator<=(const d_Timestamp itsl, const d_Timestamp its2);
operator>(const d_Timestamp itsl, const d_Timestamp Sts2);
operator>=(const d_Timestamp itsl, const d_Timestamp its2);
overlaps(const d_Timestamp isL, const d_Timestamp SeL, const d_Timestamp isR, const d_Timestamp ieR);
overlaps(const d_Timestamp isL, const d_Timestamp ieL, const d_Date isR, const d_Date ieR);
overlaps(const d_Date 4sL, const d_Date &eL,
const d_Timestamp &sR, const d_Timestamp &eR) ;
friend d_Booleanoverlaps(const d_Timestamp &sL,
const d_Timestamp &eL,
const d_Time isR, const d_Time SeR);
friend d_Booleanoverlaps(const d_Time isL, const d_Time &eL,
const d_Timestamp isR, const d_Timestamp ieR) ;
Описание
d_Timestamp()
Этот конструктор инициализирует объект текущим временем.
d_Timestamp(unsigned short year, unsigned short month = 1, unsigned short day = 1, unsigned short hour = 0, unsigned short minute = 0, float sec = 0.0)
Этот конструктор инициализирует объект заданными значениями года, месяца, дня, часов, минут и секунд.
d_Timestamp(const d_Date Sd)
Этот конструктор инициализирует объект компонентами даты, заданной параметром d.
d_Timestamp(const d_Date Sd, const d_Time St)
Этот конструктор инициализирует объект компонентами даты d и времени t.
d_Timestamp(const d_Timestamp Sts)
Этот конструктор инициализирует объект значением объекта, заданного параметром ts.
d_Timestamp s
operator=(const d_Timestamp Sts)
Этот оператор присваивает объекту значения компонентов времени и даты, заданные параметром ts.
d_Timestamp s
operator=(const d_Date Sd)
Этот оператор присваивает объекту значения компонентов даты, заданные параметром d. Компоненты времени (часы, минуты, секунды) устанавливаются равными нулю.
const d_Date S
dated const
Функция возвращает константную ссылку на составляющую даты типа d Date в объекте типа d_Timestamp.
const d_Time S
time() const
Функция возвращает константную ссылку на составляющую времени типа d_Time в объекте типа d_Timestamp.
unsigned short year() const
Функция возвращает значение года.
unsigned short.
month() const
Функция возвращает значение месяца в диапазоне 1 — 12.
unsigned short day() const
Функция возвращает значение дня (числа месяца) в диапазоне 1—31.
unsigned short hour() const
Функция возвращает значение часов в диапазоне 0—23.
unsigned short minute() const
Функция возвращает значение минут в диапазоне 0—59.
float
secondO const
Функция возвращает значение секунд в диапазоне: 0,0 <= значение < 60,0.
short
tz_hour() const
Функция возвращает количество часов по Гринвичу.
short
tz_minute() const
Функция возвращает количество минут по Гринвичу.
static d Timestamp current()
Функция возвращает текущее значение даты и времени.
d_Timestamp &
operator+=(const d_Interval &i)
Оператор инкрементирует содержащееся в объекте значение даты и времени на заданный интервал i и возвращает ссылку на этот объект.
d_Timestamp &
operator-=(const d_Interval &i)
Оператор декрементирует содержащееся в объекте значение даты и времени на заданный интервал i и возвращает ссылку на этот объект.
friend d_Timestamp
operator+(const d_Timestamp Sts, const d_Interval &i)
friend d_Timestamp
operator+(const d_Interval &i, const d_Timestamp Sts)
Каждая из этих функций суммирует значение объекта типа d_Timestamp, заданное параметром ts, и значение интервала, заданное параметром i, возвращая новое значение объекта типа d Timestamp.
friend d_Interval
operator-(const d_Timestamp &tsl, const d_Timestamp &ts2)
Функция вычитает значение объекта даты и времени ts2 из значения объекта даты и времени tsi (tsi’ - ts2), возвращая значение интервала. '
friend d_Timestamp
operator-(const d_Timestamp fits, const d_Interval &i)
Функция вычитает значение интервала i из значения даты и времени ts, возвращая новое значение объекта типа d_Timestamp.
friend d_Boolean
operator==(const d_Timestamp StsL, const d_Timestamp &tsR)
Если значения tsL и tsR равны, эта булева функция возвращает истинное значение, в противном случае — ложное.
friend d_Boolean
operator!= (const d_Timestamp StsL, const d_Timestamp &tsR)
Если значения tsL и tsR не равны, эта булева функция возвращает истинное значение. Если они равны, функция возвращает ложное значение.
friend d_Boolean
operator<(const d_Timestamp StsL, const d_Timestamp &tsR)
Если tsL меньше tsR, эта булева функция возвращает истинное значение, в противном случае — ложное.
friend d_Boolean
operator<=(const d_Timestamp StsL, const d_Timestamp StsR)
Если tsL меньше или равно tsR, эта булева функция возвращает истинное значение, в противном случае — ложное.
friend d_Boolean
operator>(const d_Timestamp StsL, const d_Timestamp StsR)
Если tsL больше tsR, эта булева функция возвращает истинное значение, в противном случае — ложное.
friend d_Boolean
operator>=(const d_Timestamp StsL, const d_Timestamp StsR)
Если tsL больше или равно tsR, эта булева функция возвращает истинное значение, в противном случае — ложное.
friend d_Boolean
overlaps (const d_Timestamp SsL, const d_Timestamp SeL, const d_Timestamp SsR, const d_Timestamp seR)
friend d_Boolean
overlaps(const d_Timestamp SsL, const d_Timestamp SeL, const d_Time SsR, const d_Time SeR)
friend d_Boolean
overlaps(const d_Date ssL, const d_Date SeL, const d_Timestamp ssR, const d_Timestamp SeR)
friend d_Boolean
overlaps(const d_Timestamp SsL, const d_Timestamp SeL, const d_Time ssR, const d_Time SeR)
friend d_Boolean
overlaps(const d_Time ssL, const d_Time &eL, const d_Timestamp ssR, const d_Timestamp SeR)
Эти дружественные булевы функции возвращают истинное значение, если период времени L (ограниченный.значениями времени sL и eL) и период времени R (ограниченный значениями времени sR и eR) включают какой-либо общий период времени. В противном случае они возвращают ложное значение.
Имя: d Transaction
Этот класс используется для транзакций базы данных. Все операции доступа, создания, модификации и удаления перманентных объектов должны выполняться внутри транзакции. Однако семантике транзакций неподвластны транзитные объекты: они не удаляются из памяти при завершении или прерывании транзакции, а их предыдущее состояние не восстанавливается, когда транзакция прерывается.
class d_Transaction { public:class d_Transaction { public:void void void void void void d_Booleanstatic d_Transaction * private:d_Transaction & };void void void void void void d_Booleanstatic d_Transaction * private:d_Transaction & };Синопсис
class d_Transaction { public:
class d_Transaction { public:
void void void void void void d_Boolean
static d_Transaction * private:
d_Transaction & };
void void void void void void d_Boolean
static d_Transaction * private:
d_Transaction & };
d_Transaction() ;
~d_Transaction() ;
begin();
commit();
abort();
checkpoint();
join() ;
leave();
is_active() const;
current();
d_Transaction(const d_Transaction &);
operator=(const d_Transaction &);
Объект класса d_Transaction нельзя скопировать или присвоить другому экземпляру класса d_Transaction, поэтому конструктор копии и оператор присваивания объявлены закрытыми (с помощью спецификатора доступа private).
Описание
d_Transaction()
Нулевой конструктор только инициализирует, но не начинает транзакцию.
~d_Transaction()
Деструктор прерывает транзакцию, если она активна.
void begin()
Эта функция начинает транзакцию. Если она вызывается несколько раз подряд без сопровождающих ее обращений к функциям commit или abort, возбуждается исключение d_Error_TransactionOpen. Эта функция неявно соединяет текущий поток с транзакцией.
void
commit()
Эта функция завершает внесение всех изменений в базу данных, которые были выполнены во время транзакции. Под изменениями подразумеваются создание, модификация и удаление перманентных объектов. Эта функция также освобож-
• дает любые блокировки, наложенные транзакцией. В отдельных реализациях баз данных допускается, чтобы объекты ссылок, т.е. экземпляры классов
d_Ref<T> и d_Ref_Any, были действительны между транзакциями. Если эта функция была вызвана без предварительного вызова функции, начинающей транзакцию, возбуждается исключение d_Error_TransactionNotOpen.
void abort()
Эта функция прерывает текущую транзакцию, освобождая все блокировки, наложенные ею. Ни одно из изменений, внесенных в перманентные объекты транзакцией, не применяется к базе данных. В отдельных реализациях баз данных допускается, чтобы объекты ссылок, т.е. экземпляры классов d_Ref<T> и d_Ref_Any, были действительны между транзакциями. Если эта функция была вызвана без предварительного вызова функции, начинающей транзакцию, возбуждается исключение d_Error_TransactionNotOpen.
void
checkpoint()
Эта функция передает базе данных все объекты, которые были модифицированы с момента последнего вызова функции checkpoint или начала транзакции. Исходная транзакция продолжается, объекты остаются в кэше и сохраняются все наложенные блокировки. Все ссылки (объекты типа d_Ref<T> и d_Ref_Any) и указатели остаются действительными. Если эта функция была вызвана без предварительного вызова функции, начинающей транзакцию, возбуждается исключениеd_Error_TransactionNotOpen.
void
join()
Эта функция соединяет текущий поток с объектом транзакции. В каждый момент времени любой поток может быть связан только с одной транзакцией. Если некоторый поток был ранее связан с другой транзакцией, он отсоединяется от нее.
void leave()
Эта функция отсоединяет текущий поток от транзакции. Поток автоматически не связывается с другой транзакцией.
d_Boolean
is_active() const
Эта функция возвращает истинное значение, если транзакция активна в данный момент, т.е. была выполнена функция begin, и транзакция еще не была прервана или завершена путем вызова функций abort или commit соответственно.
static d_Transaction * current()
Эта функция возвращает указатель на объект транзакции, с которой связан текущий поток.
Имя: il_Varrag<T>
Одномерный массив переменной длины, содержащий элементы типа т. Это коллекция, упорядоченная на основе значения индекса, а не значений элементов. Начальный индекс равен 0. Стандарт ODMG-93 1.2 включал функцию upper_bound, которая была удалена из версии ODMG 2.0. Для определения размера массива используйте унаследованную функцию cardinality.
Синопсис
template <class Т> class d_Varray : public d_Collection<T> { public:
d_Varray();
d_Varray(unsigned long sz);
d_Varray(const d_Varray<T> ia2);
~d_Varray();
d_Varray<T>ioperator™(const d_Varray<T> ia2);
voidresize(unsigned long sz);
Toperatort](unsigned long pos) const;
// Эта функция унаследована:
void d_BooleanT void voidvoid d_BooleanT void voidinsert_element(const T ival);
void d_Boolean
T void void
void d_Boolean
T void void
find_element(const T ival,
unsigned long ipos) const;
retrieve_element_at(unsigned long pos) const; remove_element_at(unsigned long pos);
replace_element_at(const T ival,
unsigned long pos);
Описание
d_Varray()
Нулевой конструктор создает пустой массив.
d_Varray(unsigned long sz)
Этот конструктор создает массив с числом элементов, заданным параметром sz. Элементы массива устанавливаются равными нулю или null-значениям в зависимости от их типа.
d_Varray(const d_Varray<T> &а)
Конструктор копии инициализирует объект массива значением аргумента а типа d_Varray<T>. Объект будет иметь те же значения элементов, что и массив а; расположены они будут в том же порядке.
~d_Varray()
Деструктор отменяет инициализацию массива, удаляя все его элементы. Если тип элементов массива имеет деструктор, он вызывается для каждого элемента.
d_Varray<T> &
operator™(const d_Varray<T> &a)
Этот оператор используется для присвоения объекту значения аргумента а. При этом элементы исходного массива удаляются.
void
resize(unsigned long sz)
Эта функция изменяет размер массива, чтобы он имел sz элементов.
т
operator!](unsigned long pos) const
Функция возвращает значение элемента, расположенного в позиции pos. Тип возврата этой функции в версии стандарта ODMG-93 1.2 был равен const т &, но в версии 2.0 заменен типом т.
void
insert_element(const Т &val)
При вызове этой функции размер массива увеличивается на 1, а в новой последней позиции массива размещается элемент, значение которого задано параметром val.
d_Boolean
find_element(const T &val, unsigned long &pos) const
Эта булева функция просматривает массив, чтобы найти элемент со значением val. Если элемент найден, функция возвращает истинное значение, а позиция найденного элемента возвращается с помощью параметра pos; в противном случае функция возвращает ложное значение. Эта функция обеспечивает средство навешивания квантора существования.
т
retrieve_element_at(unsigned long pos) const
Функция возвращает значение элемента, расположенного в позиции pos. Тип возврата этой функции в версии ODMG-93 1.2 был равен const т &, но в версии 2.0 заменен типом т.
void
remove_element_at (unsigned long pos)
Функция удаляет элемент, расположенный в позиции роз.
void
replace_element_at(const Т &val, unsigned long pos)
Функция заменяет значение элемента, расположенного в позиции роз, значением val.
Имя: STL
В версии ODMG 2.0 поддерживается подмножество коллекций стандартной библиотеки шаблонов (Standard Template Library — STL). Каждая коллекция имеет ODMG-префикс d_, но поддерживает интерфейс, определенный в библиотеке STL. Ниже представлен список этих коллекций.
d_set<T>
d_multiset<T>
d_vector<T>
d_list<T>
d_map<K,V>
d_multimap<K,V>
Для более полного ознакомления с интерфейсом, определенным в библиотеке STL, читателям предлагается обратиться к литературе, содержащей ее описание.
Приложение В
Метаклассы ODMG
В этом приложении содержится описание метаклассов, добавленных в стандарт ODMG 2.0. Схема базы данных ODMG представлена набором взаимосвязанных экземпляров этих классов. Обзор и описание взаимосвязей между этими классами приведены в разделе 16.2.
Имя: итераторы
Итератор используется для обхода связи типа “один-ко-многим”, которая существует между двумя классами в метамодели. Такие связи не представляются коллекциями напрямую, поскольку реализации баз данных имеют собственное представление метамодели. При обходе связи типа “к-одному” функция доступа возвращает ссылку на связанный объект метамодели. Но доступ к объектам метамодели, связанным между собой с помощью связи типа “один-ко-многим”, осуществляется посредством итераторов, причем здесь принят STL-стиль итерации. Каждый конец связи “ко-многим” имеет две функции: одна возвращает итератор, расположенный в начале виртуального списка связанных объектов, а другая — итератор, расположенный сразу за последним элементом в списке. Имена функций имеют суффикс (begin или end), который означает расположение итератора. Итераторы, возвращаемые этими функциями, можно использовать для опроса всех метаобъектов, участвующих в связи. Итераторы объявляются в области видимости класса метаобъекта, в котором они используются.
Синопсис
class IterType { public:class IterType { public:IterType s int int IterType & IterType const T s );IterType s int int IterType & IterType const T s );Следующий интерфейс определяет обобщенные операции, которые должен поддерживать итератор каждого типа. Этот интерфейс соответствует типу константного однонаправленного итератора, определенного библиотекой STL.
class IterType { public:
class IterType { public:
IterType s int int IterType & IterType const T s );
IterType s int int IterType & IterType const T s );
IterType () ;
IterType (const IterType s); operator=(const IterType s); operator==(const IterType s) const; operator!=(const IterType s) const; operator++();
operator++(int); operator*() const;
class attribute_iterator { public:class attribute_iterator { public:attribute_iterator & intintattribute_iterator & attribute_iterator & const d Attribute &attribute_iterator & intintattribute_iterator & attribute_iterator & const d Attribute &Любой класс представляется экземпляром класса d Class, а каждый атрибут класса — экземпляром класса d_Attribute. Тип итератора объявляется внутри области видимости класса d_Class с помощью имени attribute_iterator и используется для итерирования атрибутов данного класса. В классе attribute iterator объявлены следующие операции.
class attribute_iterator { public:
class attribute_iterator { public:
attribute_iterator & int
int
attribute_iterator & attribute_iterator & const d Attribute &
attribute_iterator & int
int
attribute_iterator & attribute_iterator & const d Attribute &
attribute_iterator();
attribute_iterator(
const attribute_iterator s); operator=(const attribute_iterator &); operator==(const attribute_iterator &) const; operator I = (const attribute_iterator &) const; operator++();
operator++(int);
operator*!) const;
В следующей строке объявляется экземпляр этого типа.
d_Class::attribute_iterator attrs;
Переменную attrs можно использовать для обхода атрибутов любого класса.
Все метаклассы для получения доступа к объектам метаклассов определенного типа используют согласованные имена типов итераторов, которые приведены в табл. ВЛ.
Таблица В.1. Итераторы для доступа к метаклассам
Имя типа итератора
Тип элемента
alias_type_iterator
d_Alias__Type
attribute_iteratoг
d_Attribute
collection_type_iterator
d_Collection_Type
constant_iterator
d_Constant
exception_iterator
d_Exception
inheritance_iterator
d_Inheritance
keyed_collection_type_iterator
d_Keyed_Collection_Type
operation_iterator
d_Operation
parameter_iterator
d_Parameter
property_iterator
d_Property
ref_type_iterator
d_Ref_Type
relationship—iterator
d_Relationship
type_iterator
d_Type
Имя: d_Access_Kind
Это перечисление определяет спецификатор доступа для свойства, операции или базового класса.
typedef enum {typedef enum {Синопсис
typedef enum {
typedef enum {
d_PUBLIC, d_PROTECTED, d_PRIVATE } d_Access_Kind;
Тип-псевдоним определяет альтернативное имя для типа. Для создания типа- псевдонима в языке C++ используется оператор typedef. Областью видимости такого типа является либо модуль, либо класс. Метод defined in, унаследованный от класса d_Meta_object, возвращает метаобъект, который представляет область видимости типа-псевдонима; это экземпляр класса d_Class или d_Module.
Синопсис
class d_Alias_Type : public d_Type ( public:
const d_Type & alias_type() const;
Описание
const d_Type & alias_type() const
Эта функция возвращает ссылку на реальный тип.
Экземпляр класса d_Attribute предназначен для описания данных-членов любого класса или структуры. Каждый атрибут имеет имя, к которому можно получить доступ, обратившись к функции name, унаследованной от класса d_Meta_object. Кроме того, атрибут имеет тип, доступ к которому возможен с помощью функции type_of, унаследованной от класса d_Property.
Синопсис
is_read_only() const; is_static() const; dimension() const;is_read_only() const; is_static() const; dimension() const;class d_Attribute : public d_Property { public:
is_read_only() const; is_static() const; dimension() const;
is_read_only() const; is_static() const; dimension() const;
d_Boolean d_Boolean unsigned long
Описание
d_Boolean
is_read_only() const
Функция возвращает истинное значение, если данный атрибут объявлен в С++ с использованием модификатора const.
d_Boolean
is_static() const
Функция возвращает истинное значение, если атрибут является статическим данным-членом класса.
unsigned long dimension() const
Если данный атрибут представляет собой одномерный массив, функция возвращает количество элементов в этом массиве. Если же атрибут не является массивом, функция возвращает значение 0.
inheritance_iterator inheritance_iterator inheritance_iterator inheritance iteratorinheritance_iterator inheritance_iterator inheritance_iterator inheritance iteratoroperation_iterator operation_iterator const d_Operation sattribute_iterator attribute_iterator const d Attribute soperation_iterator operation_iterator const d_Operation sattribute_iterator attribute_iterator const d Attribute srelationship_iterator relationship_iterator const d_Relationship &constant_iterator constant_iterator const d_Constant &type_iterator type_iterator const d_Type &relationship_iterator relationship_iterator const d_Relationship &constant_iterator constant_iterator const d_Constant &type_iterator type_iterator const d_Type &
inheritance_iterator inheritance_iterator inheritance_iterator inheritance iterator
inheritance_iterator inheritance_iterator inheritance_iterator inheritance iterator
operation_iterator operation_iterator const d_Operation s
attribute_iterator attribute_iterator const d Attribute s
operation_iterator operation_iterator const d_Operation s
attribute_iterator attribute_iterator const d Attribute s
relationship_iterator relationship_iterator const d_Relationship &
constant_iterator constant_iterator const d_Constant &
type_iterator type_iterator const d_Type &
relationship_iterator relationship_iterator const d_Relationship &
constant_iterator constant_iterator const d_Constant &
type_iterator type_iterator const d_Type &
Имя: if Class
Каждый определяемый приложением класс, экземпляры которого сохраняются в базе данных, представляется экземпляром класса d_Class. Для связывания двух классов, участвующих в отношении наследования, используется экземпляр класса d_Inheritance. Для каждого экземпляра класса d_Class можно “оглянуться” на его базовые и “увидеть" производные классы. При этом предусмотрены итераторы, по- зволяюшие получить доступ к данным- и функциям-членам класса, связям с другими классами, а также константам и типам, определенным внутри класса. Класс является областью определения своих членов. Доступ к каждому компоненту класса можно также получить, используя итератор meta_object_iterator, унаследованный от класса d_Meta_Obje c t.
Синопсис
class d_Class : public d_Type, public d_Scope { public:
/*
В области видимости этого класса определены следующие типы итераторов.
inheritance_iterator
operation_iterator
attribute_iterator
relationship_iterator
constant_iterator
type_iterator
*/
d_Booleanpersistent_capable() const;
d_Booleanhas_extent() const;
sub_class_list_begin() const; sub_class_list_end() const; base_class_list_begin() const; base_class_list_end() const;
defines_operation_begin() const;
defines_operation_end() const;
resolve_operation(const char ‘name) const;
defines_attribute_begin() const; defines_attribute_end() const;
resolve_attribute(const char *name) const;
defines_relationship_begin() const; defines_relationship_end() const; resolve_relationship(const char ‘name) const;
defines_constant_begin() const;
defines_constant_end() const;
resolve_constant(const char ‘name) const;
defines_type_begin() const;
defines_type_end() const;
resolve_type(const char ‘name) const;
Описание
d_Boolean
persistent_capable () const
Функция возвращает истинное значение, если класс обладает свойством перманентности, т.е. выведен из класса d_object.
d_Boolean
has_extent() const
Функция возвращает истинное значение, если класс имеет экстент.
inheritance_iterator
sub_class_list_begin() const
Функция возвращает итератор, размещенный в начале коллекции экземпляров класса d_Inheritance, которые представляют отношение наследования между этим классом и подклассом (производным классом).
inheritance_iterator
sub_class_list_end() const
Функция возвращает итератор, размещенный в конце коллекции экземпляров класса d_Inheritance, которые представляют отношение наследования между этим классом и подклассом (производным классом). Итерация завершается, когда итератор, возвращаемый функцией sub_class_list_begin, становится равным итератору, возвращаемому этой функцией.
inheritance_iterator
base_class_list_begin() const
Функция возвращает итератор, размешенный в начале коллекции экземпляров класса d_lnheritance, которые представляют отношение наследования между этим классом и базовым. При возврате учитывается их порядок объявления.
inheritance_iterator
base_class_list_end() const
Функция возвращает итератор, расположенный в конце коллекции экземпляров класса d_lnheritance, которые представляют отношение наследования между этим классом и базовым. Итерация завершается, когда итератор, возвращаемый функцией base_class_list_begin, становится равным итератору, возвращаемому этой функцией.
opeгаtion_iterator
defines_operation_begin() const
Функция возвращает итератор, размешенный в начале коллекции экземпляров класса d operation, которые являются функциями-членами этого класса. При возврате учитывается их порядок объявления.
operation_iterator
defines_operation_end() const
Функция возвращает итератор, размещенный в конце коллекции экземпляров класса d_Operation, которые являются функциями-членами этого класса. Итерация завершается, когда итератор, возвращаемый функцией defines_operation_begin, становится равным итератору, возвращаемому этой функцией.
const d_Operation &
resolve_operation(const char *name) const
Функция возвращает ссылку на экземпляр класса d_Operation с именем, заданным параметром паше. Этот экземпляр является элементом коллекции, к которому предоставляется доступ посредством итераторов, возвращаемых двумя предыдущими функциями.
attribute_iterator
defines_attribute_begin() const
Функция возвращает итератор, размещенный в начале коллекции экземпляров класса d_Attribute, которые представляют данные-члены этого класса. При возврате учитывается их порядок объявления.
attribute_iterator
defines_attribute_end() const
Функция возвращает итератор, расположенный в конце коллекции экземпляров класса d_Attribute, которые представляют данные-члены этого класса. Итерация завершается, когда итератор, возвращаемый функцией defines_attribute_begin, становится равным итератору, возвращаемому этой функцией.
const d_Attribute &
resolve_attribute(const char ‘name) const
Функция возвращает ссылку на экземпляр класса d_Attribute с именем, заданным параметром name. Этот экземпляр является элементом коллекции, к которому предоставляется доступ посредством итераторов, возвращаемых двумя предыдущими функциями.
relationship_iterator
defines_relationship_begin() const
Функция возвращает итератор, размещенный в начале коллекции экземпляров класса d_Relationship, которые представляют связи этого класса с другими. При возврате учитывается их порядок объявления.
relationship—iterator
defines_relationship_end() const
Функция возвращает итератор, расположенный в конце коллекции экземпляров класса d_Relationship. Итерация завершается, когда итератор, возвращаемый функцией defines_relationship_begin, становится равным итератору, возвращаемому этой функцией.
const d_Relationship &
resolve_relationship(const char ‘name) const
Функция возвращает ссылку на экземпляр класса d_Relationship с именем, заданным параметром паше. Этот экземпляр является элементом коллекции, к которому предоставляется доступ посредством итераторов, возвращаемых двумя предыдущими функциями.
constant-iterator
defines_constant_begin() const
Функция возвращает итератор, размещенный в начале коллекции экземпляров класса d_Constant, которые представляют константы, связанные с этим классом. При возврате учитывается их порядок объявления.
constant—itera tor
defines_constant_end() const
Функция возвращает итератор, расположенный в конце коллекции экземпляров класса d_Constant, которые представляют константы, связанные с этим классом. Итерация завершается, когда итератор, возвращаемый функцией defines_constant_begin, становится равным итератору, возвращаемому этой функцией.
const d_Constant &
resolve_constant (const char *name) const
Функция возвращает ссылку на экземпляр класса d_Constant с именем, заданным параметром name. Этот экземпляр является элементом коллекции, к которому предоставляется доступ посредством итераторов, возвращаемых двумя предыдущими функциями.
type_iterator
defines_type_begin() const
Функция возвращает итератор, размешенный в начале коллекции экземпляров класса d_Type, которые представляют типы, связанные с этим классом. При возврате учитывается их порядок объявления.
type_iterator
defines_type_end() const
Функция возвращает итератор, расположенный в конце коллекции экземпляров класса d_Type. Итерация завершается, когда итератор, возвращаемый функцией defines_type_begin, становится равным итератору, возвращаемому этой функцией.
const d_Type &
resolve_type(const char *name) const
Функция возвращает ссылку на экземпляр класса d_Type с именем, заданным параметром name. Этот экземпляр является элементом коллекции, к которому предоставляется доступ посредством итераторов, возвращаемых двумя предыдущими функциями.
Имя: d_Collection_TgpB
Экземпляр класса d_Collection_Type описывает конкретный вид коллекции. В классе объявляется перечисление d_Kind, используемое для обозначения вида коллекции.
Синопсис
class d_Collection_Type : public d_Type { public:
typedef enum {
LIST, ARRAY, BAG, SET, DICTIONARY, STL_LIST, STL_SET, STL_MULTISET, STL_VECTOR, STL_MAP, STL_MULTIMAP } d_Kind;
d_Kind
const d_Type i
kind() const;
element_type() const;
Описание
d_Kind
kind() const
Функция определяет вид коллекции.
const d_Type &
element_type () const
Функция возвращает ссылку на экземпляр класса d_Type, представляющий тип элементов коллекции.
Имя:й Constant
Экземпляр класса d Constant описывает значение константы, которая имеет как имя, так и тип. К имени константы можно получить доступ, вызвав функцию name, унаследованную от класса d_Meta_Object. Константа объявляется в области видимости именно того модуля или класса, доступ к которому возможен с помощью функции defined_in, унаследованной от класса d_Meta_Object.
Синопсис
const d_Type & void *const d_Type & void *constant_type() const;constant value() const;constant_type() const;constant value() const;class d_Constant : public d_Meta_Object { public:
const d_Type & void *
const d_Type & void *
constant_type() const;
constant value() const;
constant_type() const;
constant value() const;
Описание
const d_Type & constant_type() const
Функция возвращает ссылку на экземпляр класса d_Type, определяющий тип константы.
void
constant_value() const
Функция возвращает обобщенный указатель на значение константы. Этот указатель нужно привести к типу, возвращаемому функцией constant type. Значение константы изменению не подлежит.
Имя: d EnumeratiDn Tgpe
Класс d_Enumeration_Type предназначен для описания типа, экземпляры которого представляют собой некоторый набор идентификаторов: Этот класс используется для представления С++-типа enum.
Синопсис
class d_Enumeration_Type : public d_Primitive_Type, public d_Scope { public:
/*
В области видимости этого класса определен следующий тип итераторов. constant_iterator
* /
constant-iteratordefines_constant_begin() const;
constant-iteratordefines_constant—end() const;
const d_Constant &resolve_constant(const char ‘name) const;
};
Описание
constant_iterator
defines—constant—begin() const
Функция возвращает итератор, размещенный в начале коллекции экземпляров класса d Constant, которые представляют константы, связанные с этим перечислением. Константы возвращаются в порядке их объявления в перечислении.
constant—iterator
defines_constant—end() const
Функция возвращает итератор, размещенный в конце коллекции экземпляров класса d_Constant. Итерация завершается, когда итератор, возвращаемый функцией defines_constant—begin, становится равным итератору, возвращаемому этой функцией.
const d_Constant &
resolve_constant (const char ‘name) const
Функция возвращает ссылку на экземпляр класса d Constant с именем, заданным параметром name. Этот экземпляр является элементом коллекции, доступ к которому предоставляется посредством итераторов, возвращаемых двумя предыдущими функциями.
Имя: d EKceptinn
Экземпляр класса d_Exception описывает исключение, которое может быть сгенерировано несколькими операциями.
Синопсис
class d_Exception : public d_Meta_Object { public: /*
В области видимости этого класса определен следующий тип итераторов. operation_iterator
*/
const d_Type &exception_type() const;
operation_iteratorraised_in_operation_begin()const;
operation_iteratorraised_in_operation_end() const;
const d_Module idefined_in_module() const;
} ;
Описание
const d_Type &
exception_type() const
Функция возвращает ссылку на тип исключения.
operation_iterator-
raised_in_operation_begin() const
Функция возвращает итератор, размещенный в начале коллекции экземпляров класса d_Operation, представляющих операции, которые могут сгенерировать это исключение.
operation_iterator
raised_in_operation_end() const
Функция возвращает итератор, размещенный в конце коллекции экземпляров класса d operation. Итерация завершается, когда итератор, возвращаемый функцией raised_in_operation_begin, становится равным итератору, возвращаемому этой функцией.
const d_Module &
defined_in_module() const
Эта функция возвращает ссылку на модуль, который определяет область видимости этого исключения.
Имя: dlnheritancB
Каждый экземпляр класса d_lnheritance представляет связь между базовым классом и одним из производных классов. Другими словами, экземпляр класса d_Inheritance связывает два экземпляра типа d_Class отношением наследования.
Синопсис
class d_Inheritance { public:const d_Class s const d_Class & d_Access_Kind d_Booleanclass d_Inheritance { public:const d_Class s const d_Class & d_Access_Kind d_Booleanderives_from() const; inherits_to() const; access_kind() const; is virtual() const;derives_from() const; inherits_to() const; access_kind() const; is virtual() const;
class d_Inheritance { public:
const d_Class s const d_Class & d_Access_Kind d_Boolean
class d_Inheritance { public:
const d_Class s const d_Class & d_Access_Kind d_Boolean
derives_from() const; inherits_to() const; access_kind() const; is virtual() const;
derives_from() const; inherits_to() const; access_kind() const; is virtual() const;
Описание
const d—Class &
derives_from() const
Функция возвращает ссылку на базовый класс.
const d_Class & inherits_to() const
Функция возвращает ссылку на производный класс.
d_Access_Kind
access_kind() const
Функция возвращает спецификатор доступа (public, private, protected) базового класса.
d_Boolean
is_virtual() const
Функция возвращает истинное значение, если базовый класс является виртуальным.
Имя: d Keged Collectign Tgpe
- Экземпляр класса d_Keyed_Collection_Type описывает коллекцию, к элементам которой доступ осуществляется посредством ключей (сюда входят такие классы, как d_Dictionary, d_map и d_multimap). Большинство свойств этой коллекции унаследовано от класса d_Collection_Type.’
Синопсис
class d_Keyed_Collection_Type : public d_Collection_Type { public:
const d_Type ikey_type() const;
const d_Type ivalue_type() const;
};
Описание
const d—Type & key_type() const
Функция возвращает ссылку на тип ключа коллекции.
const d_Type & value_type() const
Функция возвращает ссылку на тип значения коллекции.
Имя: d Meta Oh|ect
Класс d_Meta_object является базовым для классов, используемых для представления элементов схемы, которые хранятся в словаре данных. Каждый экземпляр класса d_Meta_Object имеет имя, которое можно использовать для уникальной идентификации внутри данной области видимости (d_Scope). Некоторые экземпляры класса d_Meta_Object выведены из класса d_Scope, и в этом случае они определяют пространство имен, в котором могут быть идентифицированы по имени другие экземпляры класса d_Meta_Object.
Синопсис
class d_Meta_Object { public:
const char * const char * const d_Scope & };
name() const; comment() const; defined_in() const;
Описание
const char * name() const
Функция возвращает имя, связанное с данным метаобъектом.
const char * comment() const
Функция возвращает комментарий, содержащий некоторое описание метаобъекта.
const d_Scope &
defined_in () const
Функция возвращает область видимости, в которой определено имя этого метаобъекта.
Им»: d Module
Каждый экземпляр класса d_Module управляет логическим набором типов в словаре данных и представляет в этом словаре законченную схему. Он используется для группирования экземпляров класса d_Meta_Obj ect.
Иерархия экземпляров класса d Module представляет подсхемы или различные версии схемы. Для доступа к экземпляру класса d_Module в корне иерархии можно вызвать статическую функцию top level. Каждый экземпляр класса d_Module также является экземпляром класса d_Scope, что позволяет ему играть роль области видимости для имен в схеме базы данных. Иерархия экземпляров класса d Module позволяет определить иерархию присвоения имен.
Синопсис
class d_Module : public d_Meta_Object, public d_Scope { public: /*
В области видимости этого класса определены следующие типы итераторов. type_iterator constant_iterator operation_iterator
*/
static const d_Module i top_level(const d_Database idb);
type_iterator type_iteratorconstant_iterator constant iteratortype_iterator type_iteratorconstant_iterator constant iteratoroperation_iterator operation_iteratoroperation_iterator operation_iteratordefines_types_begin() const;
type_iterator type_iterator
constant_iterator constant iterator
type_iterator type_iterator
constant_iterator constant iterator
operation_iterator operation_iterator
operation_iterator operation_iterator
defines_types_end() const;
defines_constant_begin() const; defines_constant_end() const;
defines_operation_begin() const; defines_operation_end() const;
Описание
static const d_Module &
top_level(const d_Database &db)
Функция возвращает ссылку на экземпляр класса d_Module в вершине иерархии для базы данных, заданной параметром db.
type_iterator
defines_types_begin() const
Функция возвращает итератор, расположенный в начале коллекции экземпляров класса d_Type, которые связаны с данным модулем.
type_iterator
defines_types_end() const
Функция возвращает итератор, размещенный в конце коллекции экземпляров класса d_Type. Итерация завершается, когда итератор, возвращаемый функцией defines_types_begin, становится равным итератору, возвращаемому этой функцией.
constant_iterator
defines_constant_begin() const
Функция возвращает итератор, расположенный в начале коллекции экземпляров класса d_Constant, которые связаны с данным модулем.
constant_iterator
defines_constant_end() const'
Функция возвращает итератор, расположенный в конце коллекции экземпляров класса d_Constant. Итерация завершается, когда итератор, возвращаемый функцией defines_constant_begin, становится равным итератору, возвращаемому этой функцией.
operation_iterator
defines_operation_begin() const
Функция возвращает итератор, расположенный в начале коллекции экземпляров класса d_Operation, которые связаны с данным модулем.
operation_iterator
defines_operation_end() const
Функция возвращает итератор, расположенный в конце коллекции экземпляров класса d_Operation. Итерация завершается, когда итератор, возвращаемый функцией defines_operation_begin, становится равным итератору, возвращаемому этой функцией.
Имя: d Operation
Экземпляр класса d_Operation описывает характерные особенности операции, включающие ее имя, тип возврата и параметры, а также исключения, которые она может генерировать, и спецификацию доступа.
Синопсис
class d_Operation : public d_Meta_Object, public d_Scope { public: /*
В области видимости этого класса определены следующие типы итераторов, parameter_iterator exception_iterator
*/
const d_Type &result_type() const;
d_Booleanis_static()const;
d_Access_Kindaccess_kind() const;
parameter-iterator parameter_iterator const d Parameter Sparameter-iterator parameter_iterator const d Parameter Sexception_iterator exception_iteratorexception_iterator exception_iteratordefines_parameter_begin() const;
parameter-iterator parameter_iterator const d Parameter S
parameter-iterator parameter_iterator const d Parameter S
exception_iterator exception_iterator
exception_iterator exception_iterator
defines_parameter_end() const;
resolve_parameter(const char *name) const;
raises_exception_begin() const; raises_exception_end() const;
Описание
const d_Type &
result_type() const
Функция возвращает ссылку на экземпляр класса d_Type, представляющий тип результата.
d_Boolean
is_static() const
Функция возвращает истинное значение, если данная операция является статической функцией-членом класса.
d_Access_Kind
access_kind() const
Функция возвращает экземпляр перечислимого типа d_Access_Kind, означающий, является ли данная операция открытой (public), закрытой (private) или защищенной (protected).
parameter_iterator
defines_parameter_begin() const
Функция возвращает итератор, расположенный в начале коллекции экземпляров класса d_Parameter, которые связаны с данной операцией. Эти экземпляры являются параметрами операции и возвращаются в порядке их объявления.
parameter_iterator
defines_parameter_end(> const
Функция возвращает итератор, расположенный в конце коллекции экземпляров класса d_Parameter. Итерация завершается, когда итератор, возвращаемый функцией defines_parameter_begin, становится равным итератору, возвращаемому этой функцией.
const d_Parameter &
resolve_parameter(const char *name) const
Функция возвращает ссылку на экземпляр класса d_Parameter с заданным именем. Этот экземпляр является элементом коллекции, к которому осуществляется доступ посредством итераторов, возвращаемых двумя предыдущими функциями.
exception_iterator
raises_exception_begin() const
Функция возвращает итератор, расположенный в начале коллекции экземпляров класса d_Exception, которые связаны с данной операцией. Эти экземпляры являются исключениями, которые может генерировать эта операция.
exception_iterator
raises_exception_end() const
Функция возвращает итератор, расположенный в конце коллекции экземпляров класса d_Exception. Итерация завершается, когда итератор, возвращаемый функцией raises_exception_begin, становится равным итератору, возвращаемому этой функцией.
Имя: d Parameter
Экземпляр класса d_Parameter описывает параметр операции. Имя параметра возвращается функцией name,' унаследованной от класса d_Meta_Object. В этом классе определено перечисление d_Mode, которое используется для индикации того, предназначен ли данный параметр для ввода, вывода или для того и другого.
Синопсис
class d_Parameter : public d_Meta_Object { public:
typedef enum { IN, OUT, INOUT } d_Mode;
d_Modemode() const;
const d_Type ¶meter_type() const;
const d_Operation&defined_in_operation() const;
};
Описание
d_Mode
mode() const
Функция возвращает экземпляр класса d_Mode, который означает цель использования данного параметра в операции (для ввода данного, вывода результата или для ввода и вывода).
const d_Type &
parameter_type() const
Функция возвращает тип параметра, представленный экземпляром класса d_Type.
const d_Operation &
defined_in_operation() const
Функция возвращает операцию, с которой связан этот параметр.
Имя: d Primitive Tgpe
Экземпляр класса d_Primitive_Type представляет примитивный тип. В этом классе определено перечисление d_Type!d, которое используется для индикации конкретного примитивного типа.
Синопсис
class d_Primitive_Type : public d_Type { public:
typedef enum {
d_Type!dd_Type!dtype_id() const;type_id() const;CHAR, SHORT, LONG, DOUBLE, FLOAT, USHORT, ULONG, OCTET, BOOLEAN, ENUMERATION } d_Type!d;
d_Type!d
d_Type!d
type_id() const;
type_id() const;
Описание
d_Type!d
type_id() const
Функция возвращает экземпляр класса d_Typeld, означающий конкретный примитивный тип.
Класс d_Property — это абстрактный базовый класс для классов d_Attribute и d_Relationship. Каждое свойство имеет имя и тип. Имя можно узнать с помощью функции name, унаследованной от класса d_Meta_Object. Кроме того, каждое свойство характеризуется спецификатором доступа.
Синопсис
type_of() const; access_kind() const;type_of() const; access_kind() const;class d_Property : public d_Meta_Object { public:
type_of() const; access_kind() const;
type_of() const; access_kind() const;
const d_Type & d Access Kind
Описание
const d_Type & type_of() const
Функция возвращает тип свойства, представленный экземпляром класса d_Type.
d_Access_Kind
access_kind() const
Функция возвращает экземпляр перечислимого типа d_Access_Kind, означающий, является ли данное свойство открытым (public), закрытым (private) или защищенным (protected).
Экземпляр класса d_Ref_Type используется для представления типа, который ссылается на экземпляр заданного типа т. Он может представлять либо указатель на тип Т, либо тип d_Ref<T>. В этом классе определено перечисление d_Ref_Kind, которое используется для индикации используемой формы ссылки.
Синопсис
class d_Ref_Type : public d_Type {
public:
typedef enum { REF, POINTER } d_Ref_Kind;
d_Ref_Kindref_kind() const;
const d_Type &referenced_type() const;
) ;
Описание
d_Ref_Kind ref_kind() const
Функция возвращает экземпляр перечислимого типа d_Ref_Kind, означающий тип ссылки.
const d_Type &
referenced_type () const
Функция возвращает ссылку на экземпляр класса d_Type, представляющий тип адресуемого экземпляра.
Экземпляр класса d_Relationship представляет связь между двумя классами. Каждый конец связи имеет член перехода, который используется для получения доступа к другому концу. Член перехода представляется экземпляром класса d_Relationship, поэтому каждая связь между двумя классами представляется двумя экземплярами класса d_Relationship. Экземпляры классов d_Rel_Ref<T,мт>, d_Rel_List<T, мт> и d_Rel_Set<T, МТ> в схеме базы данных представляются экземплярами класса d_Relationship. В этом классе определено перечисление d_Rel_Kind, предназначенное для индикации используемого типа связи. Областью определения связи является класс, к которому осуществляется доступ при вызове функции defined_in_class.
Синопсис
class d_Relationship : public d_Property {
public:
typedef enum ( REL_REF, REL_SET, REL_LIST } d_Rel_Kind;
d_Rel_Kindrel_kind() const;
const d_Relationship&inverse () const;
const d_Class &defined_in_class()const;
Описание
d_Rel_Kind rel_kind() const
Функция возвращает экземпляр перечислимого типа d_Rel_Kind, означающий тип связи.
const d_Relationship & inverse() const
Функция возвращает экземпляр класса d_Relationship, представляющий член обратного перехода на другом конце связи.
const d_Class &
defined_in_class() const
Функция возвращает экземпляр класса d_Class, представляющий класс, с которым связан этот конец связи.
Имя: d Scope
Экземпляр класса d Scope используется для формирования иерархии метаобъектов. Он содержит коллекцию экземпляров класса d_Meta_Object, которые определены внутри данной области видимости. Каждый экземпляр имеет уникальное имя, соответствующее пространству имен, определенному этой областью видимости. Все экземпляры класса, выведенные из класса d_Meta_Object (за исключением класса d_Module), связаны с одним экземпляром класса d_Scope.
Синопсис
class d_Scope {
public: /*
В области видимости этого класса определен следующий тип итераторов. meta_object_iterator
*/
const d_Meta_Object &resolve(const char *name)const;
meta_object_iteratordefines_begin() const;
meta_object_iteratordefines_end() const;
);
Описание
const d_Meta_Object &
resolve (const char *name) const
Функция используется для доступа к экземпляру класса d_Meta_Object с заданным именем.
meta_object_iterator
defines_begin() const
Функция возвращает итератор, расположенный в начале коллекции экземпляров класса d_Meta Object, которые связаны с экземпляром класса d_Scope.
meta_obj ect_iterator defines_end() const
Функция возвращает итератор, расположенный в конце коллекции экземпляров класса d_Meta_Object, которые связаны с экземпляром класса d Scope. Итерация завершается, когда итератор, возвращаемый функцией defines_begin, становится равным итератору, возвращаемому этой функцией.
1 Экземпляр класса d_Structure_Type представляет структуру, определенную приложением. Он состоит из коллекции атрибутов.
Синопсис
class d_Structure_Type : public d_Type, public d_Scope { public: /*
В области видимости этого класса определен следующий тип итераторов. attribute_iterator
*/
attribute_iteratordefines_attribute_begin() const;
attribute_iteratordefines_attribute_end() const;
const d_Attribute &resolve_attribute(const char ‘name)const;
} ;
Описание
attribute_iterator
defines_attribute_begin() const
Функция возвращает итератор, расположенный в начале коллекции экземпляров класса d_Attribute, которые связаны с данной структурой. Они возвращаются в порядке их объявления.
attribute_iterator
defines_attribute_end() const
Функция возвращает итератор, расположенный в конце коллекции экземпляров класса d_Attribute, которые связаны с данной структурой. Итерация завершается, когда итератор, возвращаемый функцией defines_attribute_begin, становится равным итератору, возвращаемому этой функцией.
const d_Attribute &
resolve_attribute(const char ‘name) const
Эта функция используется для доступа к экземпляру класса d_Attribute с заданным именем.
Класс d_Type — это базовый класс каждого метакласса, используемого для представления какого-либо типа в словаре данных. Многие метаклассы имеют ссылки на типы. Класс d_Type предоставляет итераторы для доступа ко всем метаобъёктам, которые ссылаются на типы.
Синопсис
class d_Type : public d_Meta_Object { public:
/*
В области видимости этого класса определены следующие типы итераторов, alias_type_iterator collection_type_iterator keyed_collection_type_iterator ref_type_iterator property_iterator operation_iterator exception_iterator parameter_iterator constant_iterator
*/
alias_type_iteratorused_in_alias_type_begin() const;
alias_type_iteratorused_in_alias_type_end() const;
collection_type_iteratorused_in_collection_type_begin() const;
collection_type_iteratorused_in_collection_type_end() const;
keyed_collection_type_iterator
used_in_keyed_collection_type_begin() const; keyed_collection_type_iterator
used_in_keyed_collection_type_end() const;
ref_type_iterator ref_type_iteratorproperty_iterator property_iteratoroperationalterator operation_iteratorexception_iterator exception_iteratorparameter_iterator parameter_iteratorconstant_iterator constant iteratorref_type_iterator ref_type_iteratorproperty_iterator property_iteratoroperationalterator operation_iteratorexception_iterator exception_iteratorparameter_iterator parameter_iteratorconstant_iterator constant iteratorused_in__ref_type_begin () const; used_in_ref_type_end() const;
ref_type_iterator ref_type_iterator
property_iterator property_iterator
operationalterator operation_iterator
exception_iterator exception_iterator
parameter_iterator parameter_iterator
constant_iterator constant iterator
ref_type_iterator ref_type_iterator
property_iterator property_iterator
operationalterator operation_iterator
exception_iterator exception_iterator
parameter_iterator parameter_iterator
constant_iterator constant iterator
used_in_property_begin() const; used_in_property_end() const ;
used_in_operation_begin() const; used_in_operation__end () const;
used_in_exception_begin() const; used_in_exception_end() const;
used_in_parameter_begin() const; used_in_parameter_end() const;
used_in_constant_begin() const; used_in_constant_end() const;
Описание
alias_type_iterator
used_in_alias_type_begin() const
Функция возвращает итератор, расположенный в начале коллекции экземпляров класса d_Alias_Type, которые ссылаются на этот экземпляр класса d_Type.
alias_type_iterator
used_in_alias_type_end() const
Функция возвращает итератор, расположенный в конце коллекции экземпляров класса d_Alias_Type. Итерация завершается, когда итератор, возвращаемый функцией used_in_alias_type_begin, становится равным итератору, возвращаемому этой функцией.
collection_type_iterator
used_in_collection_type_begin(} const
Функция возвращает итератор, расположенный в начале коллекции экземпляров класса d_Collection_Type, которые ссылаются на этот экземпляр класса d_Type.
collection_type_iterator used_in_collection_type_end() const
Функция возвращает итератор, расположенный в конце коллекции экземпляров класса d_Collection_Type. Итерация завершается, когда итератор, возвращаемый функцией used_in_collection_type_begin, становится равным итератору, возвращаемому этой функцией.
keyed_collection_type_iterator used_in_keyed_collection_type_begin() const
Функция возвращает итератор, расположенный в начале коллекции экземпляров класса d_Keyed_Collection_Type, которые ссылаются на этот экземпляр класса d_Type.
keyed_collection_type_iterator
used_in_keyed_collection_type_end() const
Функция возвращает итератор, расположенный в конце коллекции экземпляров класса d_Keyed_Collection_Type. Итерация завершается, когда итератор, возвращаемый функцией used_in_keyed_collection_type_begin, становится равным итератору, возвращаемому этой функцией.
ref_type_iterator
used_in_ref_type_begin() const
Функция возвращает итератор, расположенный в начале коллекции экземпляров класса d_Ref_Type, которые ссылаются на этот экземпляр класса d_Type.
ref_type_iterator
used_in_ref_type_end() const
Функция возвращает итератор, расположенный в конце коллекции экземпляров класса d_Ref_Type, которые ссылаются на этот экземпляр класса d_Type. Итерация завершается, когда итератор, возвращаемый функцией used in ref type begin, становится равным итератору, возвращаемому этой функцией.
property_iterator
used_in_property_begin() const
Функция возвращает итератор, расположенный в начале коллекции экземпляров класса d_Property, которые ссылаются на этот экземпляр класса d_Type.
property_iterator
used_in_property_end() const
Функция возвращает итератор, расположенный в конце коллекции экземпляров класса d_Property, которые ссылаются на этот экземпляр класса d_Type. Итерация завершается, когда итератор, возвращаемый функцией used_in_ property_begin, становится равным итератору, возвращаемому этой функцией.
operation_iterator
used_in_operation_begin() const
Функция возвращает итератор, расположенный В начале коллекции экземпляров класса d Operation, которые ссылаются на этот экземпляр класса d Туре. operation_iterator used_in_operation_end() const
Функция возвращает итератор, расположенный в конце коллекции экземпляров класса d_Operation, которые ссылаются на этот экземпляр класса d_Type. Итерация завершается, когда итератор, возвращаемый функцией used_in_operation_begin, становится равным итератору, возвращаемому этой функцией.
exception_iterator
used_in_exception_begin() const
Функция возвращает итератор, расположенный в начале коллекции экземпляров класса d_Exception, которые ссылаются на этот экземпляр класса d_Type.
exception_iterator
used_in_exception_end() const
Функция возвращает итератор, расположенный в конце коллекции экземпляров класса d_Exception, которые ссылаются на этот экземпляр класса d_Type. Итерация завершается, когда итератор, возвращаемый функцией used_in_exception_begin, становится равным итератору, возвращаемому этой функцией.
parameter_iterator
used_in_parameter_begin() const
Функция возвращает итератор, расположенный в начале коллекции экземпляров класса d_Parameter, которые ссылаются на этот экземпляр класса d Type.
parameter_iterator
used_in_parameter_end() const
Функция возвращает итератор, расположенный в конце коллекции экземпляров класса d_Parameter, которые ссылаются на этот экземпляр класса d Type. Итерация завершается, когда итератор, возвращаемый функцией used_in_parameter_begin, становится равным итератору, возвращаемому этой функцией.
constant_iterator
used_in_constant_begin() const
Функция возвращает итератор, расположенный в начале коллекции экземпляров класса d_Constant, которые ссылаются на этот экземпляр класса d_Type.
constant_iterator
used_in_constant_end() const
Функция возвращает итератор, расположенный в конце коллекции экземпляров класса d_Constant, которые ссылаются на этот экземпляр класса d_Type. Итерация завершается, когда итератор, возвращаемый функцией used_in_ constant_begin, становится равным итератору, возвращаемому этой функцией.
Список литературы
Douglas К. Barry, The Object Database Handbook: How to Select, Implement, and Use Object-Oriented Databases, John Wiley & Sons, Inc., 1996.
C. Batini, S. Ceri, and S. Navathe, Conceptual Database Design: An Entity-Relationship Approach, Benjamin/Cummings, 1992.
R.G.G. Cattell, Object Data Management, Addison-Wesley, 1994.
R.G.G. Cattell et al., The Object Database Standard: ODMG-93 Release 1.2, Morgan Kaufmann, 1996.
R.G.G. Cattell et al., The Object Database Standard: ODMG 2.0, Morgan Kaufmann. 1997.
P.P. Chen, “The Entity-Relationship Model: Toward a Unified View of Data.” ACM Transactions on Database Systems 1, no. 1 (March 1976): 9—37.
P.P. Chen, “The Entity-Relationship Model: A Basis for the Enterprise View of Data.” Proceedings IFIPS NCC 46, no. 46 (1977): 76—84.
К.Дж. Дейт, Введение в системы баз данных, 6-е издание: Пер. с англ,—К.; М.; СПб.: Издательский дом “Вильямс”, 2000.
Martin Fowler, UML Distilled: Applying the Standard Object Modeling Language, Addison-Wesley, 1997.
Peter Gray, Logic, Algebra and Databases, Ellis Horwood, 1984.
Barbara Liskov and John Guttag, Abstraction and Specification in Program Development, McGraw-Hill, 1986.
Mary E.S. Loomis, Object Databases: The Essentials, Addison-Wesley, 1995.
Jim Melton, “SQL3: Moving into the Future,” Proceedings of the Object/Relational Summit, 1996.
Ronald Ross, Entity Modeling: Techniques and Applications, Database Research Group, 1987.
377377Список литературы
377
377
Предметный указатель
1
EOF, 26
ER-модель, 38; 39
О
1НФ, 246
3
F
Object Definition Language, 29; 225
Fortran, 244
Object Query Language, 30;
3GL, 244
4
G
88; 140 ODBC, 257
GMT, 57; 333; 334
ODL, 29; 225
4GL, 140
Greenwich Mean Time, 57;
ODMG, 16; 20
А
333
T
odmg.h, 37
Open DataBase Connectivity.
ACID, 33
257'
allocator, 95
IDL, 29; 49
operatorj], 53
Interface Definition
operator<<, 22; 52
В
Language, 29; 49
operator>>, 22
BCD, 51
iostreams, 22
OQL, 17; 30; 88; 140; 144;
binary coded decimal, 51
IPC, 212
255
BLOB, 46; 47
lS-A-связи, 62
коллекции, 154
B-tree, 90
istream, 22
выравнивание, 160
массив (array), 155
c
J
множество (set), 154
catch, 37
Java, 29; 94; 254; 258
мультимножество
cfront, 224
Java Virtual Machine, 258
(bag), 154
Cobol, 244
JDBC, 258
список (list), 155
Composite object, 43
ЛТ-компиляторы, 259
литерал коллекции, 154
COP, 16
JVM, 258
оператор
CORBA, 29; 49; 203
D
L
неравенства, 162 равенства, 162
LRU, 204
операторы
d_False, 26; 49; 265
арифметические
d_True, 26; 49; 265
N
бинарные, 150
Domain, 40
Network File System, 213
унарные, 150
NFS, 213
булевы, 152
E
nil, 147; 148
приоритет, 164
Entity, 39
null-конструктор, 25
совокупные, 156
Entity-relationship, 38
сравнения, 151
строковые выражения, 152
функции, 163
ostream, 22
Р
Persistence-capable class, 42
Persistent Stored Modules, 250
PowerBuilder, 244
R
Relationship, 39
s
Schema capture tool, 23
Simula-67, 242
Smalltalk, 29; 94
SQL, 140
SQL PSM, 250; 251
SQL3, 30; 242; 249; 257
SQL-оператор
create table, 74
update, 75
Standard Template Library, 94; 226
STL, 94; 226; 274; 344
Subobject, 43
swizzling, 80
T
TCP/IP, 212
template, 43; 93
try, 37
u
UML, 21
UML-запись, 112
Unified Modeling Language, 21; 112
UNIX, 211
V
vtbl-указатель, 71; 236
w
Web-узел ODMG, 29
A
Абстрактные типы
данных, 50; 250
Абстракция сущности, 61
Активизация, 67; 68
Алгоритм
for_each, 100
Аплет, 258
Архитектура
баз данных, 207
клиент/сервер, 195
Архитектура объектных
баз данных, 190
Ассоциативный доступ, 89
Ассоциативный массив, 96
АТД, 50; 250
Атомарные литеральные
выражения, 150
Атрибут
производный, 64
Атрибуты, 40; 61; 230
данных, 63
Аттестация
Cartel, 214
007, 214
Б
Байт-код, 258
Б-дерево, 90
Библиотека C++
iostreams, 22
Битовые поля, 60
Блокировка
без взаимного доступа, 201
Блокировки
защелки, 198
преднамеренные, 201
В
Виртуальная машина Java, 258
Виртуальная функция
C++, 148
Выкачка объектов, 204
Выражение определения
запроса, 179
Выражение пути, 106; 128
Вычитание множеств, 102
Г
Гетерогенность, 83
архитектуры, 191; 196
Группа баз данных, 32
Групповой предикат, 177
д
Данные-члены
статические, 63
Дезактивизация, 67; 68
Декартово произведение,
168
Деструктор, 28; 67
Динамическая проверка типов, 89
Директива from, 165; 169 group by, 174 having, 177 order by, 178 select, 165; 172 where, 165; 171
Дисковые массивы, 211
Домен, 40; 42; 46; 228
Доменные типы, 97
3
Заголовочные файлы, 37
Заголовочный файл odmg.h, 24; 37
Замкнутый доступ, 105
Замыкание, 105
Замыкание объектов, 192 полное, 192 транзитивное, 192
Запрос select, 167; 181
И
Идентификатор объекта, 62
Идентификационное равенство, 82
Иерархия вложенности объектов, 130
Иерархия классов, 192
Именованная коллекция, 103
Именованные объекты, 145
Именованный запрос, 179
Именованный объект, 88
Именованный тип строк, 252
Имя объекта, 87
Индекс, 89; 217; 249
в реляционных базах данных, 110
со структурой Б-дерева, 90
пути, 129
Инициализация экземпляров, 66
Инкапсуляция, 41; 43; 48;
245
Интерфейс
JDBC, 258
ODBC, 258
Интерфейсы связей, 115
Исключение
d_Error_DatabaseClassMi smatch, 238; 288
d_Error_DatabaseClassUn defined, ,288
d_Error_DatabaseClosed, 277; 288
d_Error_DatabaseOpen, 276; 288
d_Error_Date!nvalid. 54;
288
d_Error_ElementNotFoun d, 284; 285; 288
d_Error_IteratorDifferent
Collections, 288; 296
d_Error_IteratorExhauste
d, 100; 288; 296; 297
d_Error_IteratorExhauste d., 297
d_Error_l teratorN ot Back ward, 99; 288
d_Error_lteratorNotBack ward., 297
d_Error_MemberIsOfInva lidType, 288
d_Error_MemberNotFoun d, 288
d_Error_NameNotUnique
, 88; 277; 288
d_Error_PositionOutOfRa nge, 102; 289
d_Error_QueryParameter Countinvalid, 185; 289; 303
d_Error_QueryParameter Typeinvalid, 185; 289; 303
d_Error_RefInvalid, 121;
148; 234; 289
d_Error_RefNul, 318
d_Error_RefNull, 80; 162;
289; 308
d_Error_Time!nvalid, 57;
289
d_Error_TimestampInvali d, 289
d_Error_TransactionNotO pen, 289; 341
d_E rror_T ransactionOpen
, 289; 340
d_Error_TypeInvalid, 79;
89; 289; 308; 317
Исключения, 32; 36
Итератор, 93; 295; 346
Итераторный объект, 93
Итерация, 99
К
Кардинальное число, 93 связи, 113
Категория типов, 41 Квантор
общности, 166 существования, 166 Класс
d_Association, 284 d_Bag<T>, 96; 268 d_Collection, 94 d_Collection<T>, 94: 97;
183; 272
d_Database, 25; 33; 88;
276
d_Date, 54; 278 d_Dictionary<K,V>, 96;
284
d_Error, 36; 286 d_Extent, 104 d_Extent<T>, 290 d_Interval, 53; 292 d_Iterator<T>, 94; 99;
295
d_List<T>, 96; 102; 298 d_Object, 23; 29; 62; 68;
69; 77; 95; 301 d_OQL_Query, 185; 303 d_Ref_Any, 77; 310 d_Ref<T>, 24; 63; 67; 77;
307
инициализация, 78 d_Rel_List<T, MT>, 312 d_Rel_List<T, MT>, 117 d_Rel_Ref<T, MT>, 316 d_Rel_Ref<T, MT>, 117 d_Rel_Set<T, MT>, 117 d_Rel_Set<T,MT>, 320 d_Set<T>, 95; 101; 325 d_String, 52; 329 d_Time, 57; 332 d_Timestamp, 58; 336 d_Transaction, 25; 34; 70;
340
d_Varray<T>, 96; 102;
342
exception, 36 istream, 22 ostream, 22 Класс C++
exception, 286; 287 Классы, 230
ODMG, 23; 267 абстрактные, 29 конкретные, 29
Кластер, 109
Кластеризация, 135
объектов, 194 экземпляров, 66 Ключевое слово
as, 170
distinct, 168; 177
Ключи,89
Коллекции, 43; 229 конструктор копии, 98 массив, 96 множество, 95 мультимножество, 96 нулевой конструктор, 98 оптимизация, 108 отображение, 96 поддержка в OQL, 154 словарь, 96 список, 96
Коллекция, 51; 93
в реляционной модели данных, 109
именованная, 103
кардинальное число, 106 Композиция функций, 143 Компоненты архитектуры, 190
Конструктор, 66 копии,81 нулевой, 51
Контекст объекта, 63
Конфликт блокировок, 32;
73
Копирование ссылки, 81
Корневой объект, 88 Курсор, 109
Кэтл Рик, 214
Кэш
базы данных, 31; 197;
200; 208
горячий, 210
клиента, 31; 208 холодный, 209 объектов, 29 приложения клиента, 202
Кэш-неудача, 208
Кэш-попадание, 208
м
Массив, 96
Массив C++, 50
Метакласс, 223; 226 attribute_iterator, 346 d_Alias_Type, 233; 349 d_Attribute, 230; 350
d_Class, 230; 351 d_Collection_Type, 229;
355
d_Constant, 227; 356
d_Enumeration_Type, 233; 357
d_Exception, 228; 358
d_Inheritance, 231; 351;
359
d_Keyed_Collection_Type , 229; 360
d_Meta_Object, 226; 361
d_Module, 227; 362
d_Operation, 227; 364
d_Parameter, 366
d_Primitive_Type, 228;
367
d_Property, 229; 368
d_Ref_Type, 228; 369
d_Relationship, 231; 370
d_Scope, 226; 371
d_Structure_Type, 230;
372
d_Type, 227; 373
Метаобъект, 223
Миграция экземпляров, 236
Множества
вычитание, 101
объединение, 101
пересечение, 101
Множество, 95
Модель данных типа
сущность-связь, 38
Модификатор
const, 72
Модификация
экземпляров, 71
Модуль, 227
Мультимножество, 96; 268
н
Навешивание
квантора общности, 166
квантора существования, 166; 275
Наследование, 62; 231
Нулевой конструктор, 25;
51
Нулевые ссылки, 79
О
Области видимости имен, 135; 181
Область видимости
объектных идентификаторов, 76
Обратная связь, 114 Обратная ссылка, 114 Объединение множеств, 101
Объект перманентный, 31 создание в OQL, 149 транзитный, 31
Объект точки входа, 88
Объектная модель, 29
Объектная парадигма, 30 Объектный идентификатор, 63; 76 состав, 85
Ограничение кардинального числа, 114 семантической целостности, 41
Оператор
delete, 28; 67
in, 170
new, 62; 65; 195; 301;
302
присваивания, 73 разыменования, 26; 77
Операторы неравенства, 82 равенства, 82
Операции, 61; 64 разыменования, 80 delete_object, 67 dynamic_cast<T2>, 148 замыкания, 105; 137 разыменования, 147 разыменования operator*, 100
Определение запроса, 179
Оптимизатор запросов, 152
Оптимизация коллекций, 108 производител ьности ,212
Организация базы данных, 192 логическая, 192 физическая, 193
Отношения наследования, 62; 267 между коллекциями, 94
Отображение, 96
п
Парадигма
моделирования, 38
Параллелизм, 73; 200; 213
Первая нормальная форма, 48
Первичный ключ, 249 Переменная итератора, 165; 170
Пересечение множеств, 102
Перечисление d_Access_Kind, 348 d_Kind, 229 d_Ref_Kind, 228; 369 d_Type!d, 228
Month, 55
Time_Zone, 58
Weekday, 55
Перечисления, 233 Перманентность, 22 Подзапрос, 180 Подмножество, 101 Подобъект, 43; 130; 132
Подстановка, 80
Полиморфизм, 71; 148
Полиморфные ссылки, 77
Постпроцессор, 225
Поток, 32; 196 ostream, 54
Правило супермножества, 105
Предикат, 165 навешивания квантора общности, 165
квантора существования, 165
составной, 166 Представление, 75 Преобразования коллекций, 159
Препроцессор, 225
Приведение типа, 148
Примитивные доменные типы OQL, 150
литеральные типы, 48
Примитивы ER- моделирования, 39
Проверка типов, 79 Прозрачность базы данных, 28
Прозрачный доступ, 83
Производительность, 207;
223
Производный атрибут, 64
Пространства имен, 24
Протокол
NFS, 213 ’
TCP/IP, 198; 212
двухфазного завершения транзакции, 191
Процесс приложения, 31 сервера базы данных, 31; 197
Псевдонимы типов, 233
Р
Равенство объектов, 74
Разыменование, 147
Разыменование ссылки, 28
Распространение операции, 137 удалений, 67
Рекурсивные связи, 115
Рекурсия, 114
Реляционное извлечение столбцов, 248 строк, 248
Реляционное исчисление доменов, 170 кортежей, 170
Реляционное соединение, 248
Реляционные базы данных, 74
С
Свойства, 229 класса, 63 объектов, 29 сущности, 42; 61
Связи, 61; 112; 130; 231
Связь, 39
двунаправленная, 114 кардинальное число, 113 обратная, 114 однонаправленная, 114 рефлексивная, 115
Сегмент, 193
Сервер реляционной базы данных, 47
Сеть баз данных, 32
Символ препроцессора _ _ODMG_93_ _, 266
Словарь, 96
Словарь данных, 223
Сложный объект, 130
Согласованность кэшей, 205
Соединение, 220
Создание базы данных, 23
Составная сущность, 41;
130
Составной базовый объект, 130; 134
Составной объект, 73; 130
Составные сущности, 43
Составные типы данных, 50
Спецификатор transient, 63
Спецификатор доступа private, 63 public, 63
Список, 96
Сравнение объектов, 74
Средство захвата схемы, 23; 64
Ссылка копирование, 81 обратная, 114
Ссылки, 228 полиморфные, 77
Ссылочная целостность, 112; 119
Стандартная библиотека шаблонов, 94
Статические данные- члены, 63
Степень связи, 113
Страница, 194
Структура, 50
создание в OQL, 149
Структуры, 230
Субтранзакция, 35
Супермножество, 101 действий, 105
Сущности, 42
Сущность, 39; 61 в реляционной базе данных, 74 составная, 41; 43 составная, 130
Схема
средство захвата, 23 базы данных, 62; 223;
226 логическая, 39 модификация, 233 физическая, 39
Схема вытеснения по давности использования, 204; 209
т
Таблица, 243
Таблица виртуальных функций, 71
Тип
bool, 49; 265
char, 48
class, 50
d_Boolean, 26; 49; 265
d_Boolean (ODMG), 49
d_Char, 49 d_Double, 49 d_Float, 49 d_Long, 49 d_Octet, 49 d_Short, 49 d_ULong, 49 d_UShort, 49 double, 48 float, 48 int, 48 long, 48 short, 48 struct, 50 указатель, 48
Типы данных абстрактные, 50 составные, 50
Том, 193
Транзакция, 25; 32; 33; 64
Транзитные члены, 63
Транзитный экземпляр класса, 22
Транслятор C++ cfront, 224
Триггер, 64
Тупиковые ситуации, 201
У
Удаление объекта, 67 экземпляров класса, 28
Унифицированный язык моделирования, 112
Управление параллелизмом, 200 оптимистическая форма,
73
пессимистическая форма, 73
Условие соединения, 179
Утилита захвата схемы, 225
ф
Федеративная база данных, 32
Флаг
бит исправлений, 72 Флаг типа, 59
Функции трансформации, 237
Функциональная модель данных, 64
Функция abort, 35 access_kind, 229 advance, 99 alias_type, 233 append, 102 begin, 34 cardinality, 97; 102 checkpoint, 35; 70 clear, 79 close, 33 commit, 35; 210 concat, 102 contains_element, 97 create_iterator, 99 current, 35; 54; 57; 58 d_activate, 69 d_deactivate, 69 d_oql_execute, 185; 303 defined_in, 226 delete_object, 68 dimension, 230 exception_type, 228 exists_element, 183 fill, 58 get_element, 100
insert_element, 97; 102 is_null, 26; 80 is_read_only, 230 is_static, 228; 230 is_valid_date, 54 join, 35 leave, 35
lookup_object, 26; 88; 218 mark_modified, 27; 29;
71; 98; 108; 202
next, 100
not_done, 99
open, 33
operator*, 27; 80 operator+, 102 operator+=, 102 operator<<, 27 operator->, 27 operator—>, 80 overlaps, 56
para mete r_type, 228
ptr, 28; 80
query, 184 remove_all, 98 remove_element, 98 rename_object, 88 resize, 102 resolve, 226 select, 184 select_element, 184 set_object_name, 88; 145 top_level, 227 type_id, 228 what, 36
распознавания объединения, 60
X
Хеш-значение, 89
Хеш-индекс, 90
Хеш-таблица, 87; 90
Хеш-функция, 90
ш
Шаблон, 93
Шаблонные классы, 114-
117
э
Экземпляр класса транзитный, 22
Экземпляр сущности в реляционной базе данных, 74
Экземпляры классов, 62
создание, 65
создание перманентных экземпляров, 66
создание транзитных экземпляров, 65
Экстент, 29; 103; 104; 290
Эталонные тесты
производительности, 207
Я
Язык объектных запросов, 30; 88; 140; 144; 165; 183
Язык определения объектов, 29