Реализация высокоуровнего интерфейса вокруг базы данных Berclee DB

Тип работы:
Курсовая
Предмет:
Программирование


Узнать стоимость

Детальная информация о работе

Выдержка из работы

МИНИСТЕРСТВО НАУКИ И ОБРАЗОВАНИЯ УКРАИНЫ

ОДЕССКИЙ НАЦИОНАЛЬНЫЙ УНИВЕРСИТЕТ

им. И.И. Мечникова

ИНСТИТУТ МАТЕМАТИКИ, ЭКОНОМИКИ И МЕХАНИКИ

курсовая работа

На тему

«Реализация высокоуровнего интерфейса для работы с базой данных Berkeley DB»

студента 5 курса

кафедры математического

обеспечения компьютерных систем

Трофимова Бориса

Научный руководитель:

доц. Каменева А. В.

Одесса 2004

Содержание

1. Введение

2. Основные сведения из баз данных

· основные определения и классификация

· типы пользователей к БД

· механизм транзакций и курсоров

3. Основные сведения из Berkeley DB

4. Основные сведения по программной системе генерации языков программирования YAPP

5. Структура разрабатываемой программы

· Ядро, включающее в себя библиотеку классов и все необходимые для работы стаба инструменты

· Лексический анализатор, его структура

· Синтаксический анализатор, его структура

· Семантика, генерация С++ стабов (автоматически сгенерированный программный код)

6. Пример работы программы

7. Заключение

8. Список используемой литературы

9. Приложения

1. Введение

В современном информационном мире, пожалуй, нет места, где бы не использовались информационные хранилища данных — базы данных. Они нужны во всех отраслях информационного хозяйства, в них хранится информация про курсы валют, бронирование авиабилетов, координаты спутника и т. д. Естественно, доступ к таким данным может осуществляться в режиме реального времени, следовательно время, скорость реакции программной системы — это один из самых существенных критериев.

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

1. Реляционная модель является универсальной, то есть с ее помощью в принципе можно реализовать любую схему данных.

2. Реляционная модель имеет под собой математический аппарат реляционной алгебры, она более формализована, нежели остальные модели.

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

Однако за универсальность нужно платить. И одним из слабых мест у реляционных СУБД является скорость выполнения запроса! Конечно, создаются интеллектуальные препроцессоры, оптимизирующие запрос, а также время его выполнения (так, например, такой есть в InterBase, Oracle, Informix), но проблема заключается в самой модели. Были проведены эксперименты, в ходе которых работа с навигационно-сетевой СУБД (Berkeley DB) была эффективней на порядок, чем с реляционной СУБД (Informix).

Таким образом, сетевые СУБД работают быстрей, однако они более запутанней, их семантика более сложная, чем реляционная, практически недоступная для понимания конечного пользователя. Интерфейс к такой базе данных обычно является низкоуровневым. Возникает вопрос, а нельзя ли как-нибудь поднять уровень абстракции, сделать интерфейс к ней более понятным и простым для программиста. Это послужило основой для данной работы.

Итак, постановка задачи:

Сформировать транслятор генерации объектно-ориентированного интерфейса на С++ для работы с низкоуровневой СУБД BerkeleyDB по заданной пользователем схеме данных.

2. Основные сведения из баз данных

База данных — это поименованная совокупность структурированных данных, относящихся к определенной предметной области.

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

Система управления базами данных (СУБД) — это комплекс программных и языковых средств, необходимых для создания баз данных, их поддержания в актуальном состоянии и организации в них поиска необходимой информации. Другими словами, СУБД предоставляет интерфейсы для доступа к данным, их модификации, а также хранения.

К основным функциям СУБД принято относить следующие:

· управление данными во внешней памяти;

· управление буферами оперативной памяти;

· управление транзакциями;

· журнализация и восстановление БД после сбоев;

· поддержка языков БД;

Любая СУБД обеспечивает как минимум две услуги.

Первая услуга является доступом к данным. Она означает, что пользователь должен иметь возможность добавлять, удалять, модифицировать данные. Все современные СУБД обеспечивают эти услуги.

Вторая услуга является управлением данных. Управление Данных более сложное, чем доступ к данным. Например, услуги управления данными включают возможность параллелизма. Параллелизм означает, что многочисленные пользователи могут работать с БД в одно и тоже время.

Базы данных по доступу к данным можно разделить на следующие категории:

· Базы данных с низкоуровневым интерфейсом, такие базы не имеют встроенных средств определения типов данных и работают с «сырыми» блоками данных. Они обладают обычно достаточно быстрым доступом к данным.

· Базы с высокоабстрактным интерфейсом доступа к данным. В комплект такого интерфейса входят средства определения пользовательских данных, а также доступа к ним. Такими базами являются все реляционные, объектно-ориентированные, дедуктивные.

Соответственно интерфейсы к ним также имеют разделение, главным образом в зависимости от типа БД. Для нас наиболее важным будет объектно-ориентированный интерфейс работы с данными (не обязательно объектами). Такой интерфейс предусматривает только объектно-ориентированную работу с данными, но не работу с объектно-ориентированными данными. Его построение и использование обуславливается чаще всего неудобством работы с низкоуровневыми БД.

Пользователи системы баз данных

Здесь под пользователем СУБД будем понимать субъект (пользователь, программист, прикладная программа), взаимодействующий с каким-нибудь интерфейсом к ней. Пользователей можно разбить на несколько категорий:

Первая — системные, отвечающие за программирование, например оболочек для базы. У нас это субъект, который формирует стабы по заданной схеме хранения данных.

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

Третья — конечные пользователи, которые работают с базой данных непосредственно через интерфейс-приложение. Он в отличие от первых двух категорий работает не схемой хранения данных, а непосредственно с самими данными: добавляет, удаляет или модифицирует их по необходимости.

Механизм транзакций и курсоров

Транзакция — это последовательность операций над БД, рассматриваемых СУБД как единое целое. Либо транзакция успешно выполняется, и СУБД фиксирует (COMMIT) изменения БД, произведенные этой транзакцией, во внешней памяти, либо ни одно из этих изменений никак не отражается на состоянии БД, т. е. происходит ее откат. Поддержание механизма транзакций необходимо для поддержания логической целостности БД при работе одновременно с несколькими пользователями, то есть в многопользовательских СУБД.

Итак, транзакция обладает следующими свойствами:

· Атомарность — либо транзакция принимается целиком, либо вообще нет.

· Согласованность — транзакция начинает выполняться при целой БД и переводит БД, в случае успешного завершения, также в целостное состояние

· Изолированность — выполнение одной транзакции не влияет на выполнение другой.

· Устойчивость — выполнение транзакции не должно привести к краху БД

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

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

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

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

· Первый уровень — отсутствие потерянных изменений. Рассмотрим следующий сценарий совместного выполнения двух транзакций. Транзакция 1 изменяет объект базы данных A. До завершения транзакции 1 транзакция 2 также изменяет объект A. Транзакция 2 завершается оператором ROLLBACK (например, по причине нарушения ограничений целостности). Тогда при повторном чтении объекта A транзакция 1 не видит изменений этого объекта, произведенных ранее. Такая ситуация называется ситуацией потерянных изменений. Естественно, она противоречит требованию изолированности пользователей. Чтобы избежать такой ситуации в транзакции 1 требуется, чтобы до завершения транзакции 1 никакая другая транзакция не могла изменять объект A. Отсутствие потерянных изменений является минимальным требованием к СУБД по части синхронизации параллельно выполняемых транзакций.

· Второй уровень — отсутствие чтения «грязных данных». Рассмотрим следующий сценарий совместного выполнения транзакций 1 и 2. Транзакция 1 изменяет объект базы данных A. Параллельно с этим транзакция 2 читает объект A. Поскольку операция изменения еще не завершена, транзакция 2 видит несогласованные «грязные» данные (в частности, операция транзакции 1 может быть отвернута при проверке немедленно проверяемого ограничения целостности). Это тоже не соответствует требованию изолированности пользователей (каждый пользователь начинает свою транзакцию при согласованном состоянии базы данных и в праве ожидать видеть согласованные данные). Чтобы избежать ситуации чтения «грязных» данных, до завершения транзакции 1, изменившей объект A, никакая другая транзакция не должна читать объект A (минимальным требованием является блокировка чтения объекта A до завершения операции его изменения в транзакции 1).

· Третий уровень — отсутствие неповторяющихся чтений. Рассмотрим следующий сценарий. Транзакция 1 читает объект базы данных A. До завершения транзакции 1 транзакция 2 изменяет объект A и успешно завершается оператором COMMIT. Транзакция 1 повторно читает объект A и видит его измененное состояние. Чтобы избежать неповторяющихся чтений, до завершения транзакции 1 никакая другая транзакция не должна изменять объект A. В большинстве систем это является максимальным требованием к синхронизации транзакций, хотя, как мы увидим немного позже, отсутствие неповторяющихся чтений еще не гарантирует реальной изолированности пользователей.

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

Курсоры представляют собой совершенно другую сущность БД. Одной из самых распространенных операций с БД является предоставление набора информации по запросу пользователя. В реляционной БД это организуется через конструкцию «select …». Итак, результатом выполнения такого запроса будет набор данных, взятый из БД. Однако как получить доступ к этим данным? В соответствии с новой парадигмой ООП — шаблонами проектирования, определяется некоторый объект, называемый курсором, который выполняет функции простого итератора. Фактически через его интерфейс пользователь в состоянии перебирать все данные, хранящиеся в полученном наборов произвольном порядке.

Такой объект имеет обычно такой интерфейс:

Init (…); создание итератора

Bool GetNextInfo (); перейти на следующую порцию данных

GetCurrData (); получить текущую порцию данных

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

Естественно, понятие курсора тесно связано с механизмом транзакций.

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

3. Основные сведения из BerkeleyDB

Berkeley DB — «open source» библиотека баз данных, которая обеспечивает масштабируемое, быстродействующее, управление данных, их защиту в приложении. Berkeley DB обеспечивает простой функциональный вызов API для доступа к данным и их управления для множества языков программирования, включая C, C++, Java, Perl, Tcl, Pyton, и PHP. Все операции с базой совершаются в библиотеке. Низкий уровень операций включает в себя механизм блокировок, транзакционных блокировок, коллективного буферного управления, управления памяти и т. п.

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

Библиотека является достаточно портативной. Она работает под почти всеми UNIX и вариантами Linux, Windows, и множеством других операционных систем в реальном времени. Она работает как на 32- бите так и 64-битовых системах.

Сама база данных библиотеки является чрезвычайно компактной (под 300 килобайтами текстового пространства в общей архитектуре), но она может управлять базами данных вплоть до 256 terabytes. Она также поддерживает высокий параллелизм, с тысячами пользователей, действующих на той же базе данных в то же самое время.

Приложения Berkeley DB содержат достаточное количество схем хранения данных, которые наилучшим образом подходят приложению. Berkeley DB поддерживает таблицы типа Hash, Btrees, простые очереди с числовым доступом к данным и устойчивые очереди. Программисты могут создать таблицы, использующие любую из этих структур памяти, и могут смешать операции в других типах таблиц в своем приложении.

Таблицы Hash обычно хороши для очень больших баз данных, когда необходим поиск и разумное время коррекции для произвольного доступа записей. Таблицы Hash позволяют спрашивать, «этот объект существует?» или, чтобы выбирать запись с известным объектом. Таблицы Hash не позволяют, например, требовать записи с объектами, которые близки к известному объекту. Btree используется для поисков, базирующихся на диапазонах, когда приложению нужно находить все записи с объектами между некоторым начальным значением и концом. Btree также подходит для организации ссылочной зависимости. Структура Btree хранит близкие данные рядом в памяти (на диске), так что при выборе соседних величин обычно не требуется дисковый доступ. Очереди, основанные на числовой индексации записей. Каждая запись имеет уникальный номер. И поиск, удаление, изменение записи осуществляется через этот номер. Berkeley DB генерирует эти рекордные номера автоматически.

Berkeley DB поддерживает наиболее важные услуги управления данными, включая параллелизм, транзакционность и восстановление, страничное управление кэшем. Все они работают для любых вариантов хранения данных.

Berkeley DB не является сервером баз данных. Так как библиотека для работы с Berkeley загружается в адресное пространство приложения и доступно только для него. Хотя такое решение реализуемо.

Итак, Berkeley DB состоит из следующих объектов: Dbt, Db, DbEnv. Они связаны следующим образом

4. Основные сведения по программной системе генерации языков программирования YAPP

YAPP представляет собой программную систему с использованием Perl для генерации и использования синтаксических анализаторов LALR. Фактически это коллекция модулей расширения, написанных на Perl, совместимая с форматом YACC и позволяющих генерировать perl-код.

Пользователь формирует файл с грамматикой, описывающей некоторый желаемый язык. Этот файл подается на вход к yapp

yapp grammar_file. yp

На выходе получаем perl-модуль, выполняющий синтаксический анализатор языка, описываемого пользователем. То есть, фактически, yapp и генерирует синтаксические анализаторы.

Чтобы подключить синтаксический анализатор, пользователь должен подготовить уже своими силами лексический анализатор и использовать примерно такой код:

use MyParser;

$parser=new MyParser ();

$value=$parser-> YYParse (yylex => & lexer_sub, yyerror => & error_sub);

Файл грамматики

1) Комментарии бывают в стиле Perl # или в стиле С //, /* */.

2) Признаки литералов и строк.

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

Синтаксис нетерминальных символов и символьных лексем:

[A-Za-z][A-Za-z0−9_]*.

Запрещено использование название «error» для литералов.

Структура его выглядит следующим образом (очень похожа на yacc, фактически является ее подмножеством)

Файл состоит из трех секций, разделенных %%:

заголовок

%%

секция правил

%%

нижняя секция

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

Она содержит также декларации приоритета, представленных %left, %right и %nonassoc (определяющ. ассоциативность).

%start указывает на правило (левую часть), выполняющееся первым.

Секция правил содержит грамматические правила:

Каждое правило состоит из слева лежащего символа (нетерминального), разделенного ':' и одним или несколькими возможными правилами, разделенными '|' и завершенными '; ':

exp: exp '+' exp

| exp '-' exp

;

Правило справа может быть пустым

input: #empty

| input line

;

Для задания явного приоритета в случае неоднозначности следует использовать директиву %prec, дающую правилу высокий приоритет.

exp: '-' exp %prec NEG { -$_[1] }

| exp '+' exp { $_[1] + $_[3] }

| NUM

;

Примечательно, что YAPP позволяет встраивать в синтаксический анализатор семантику. Это организуется путем добавления в конце правила конструкций {…}, ограничивающих Perl-команды. Они встраиваются в синтаксический анализатор и выполняются после применения анализатором этого правила. Такой код может возвращать некоторую величину, используемую в определении следующего правила по дереву.

Переменные $_[1], $_[n] являются параметрами и хранят значения разобранного правила.

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

5. Структура разрабатываемой программы

Идеология оболочки состоит в том, что пользователь будущей базы данных сначала описывает на специальном языке структуры данных, из которых должны состоять таблицы в базе (то есть фактически интерфейсы) их ссылочные связи, индексы и т. п. Затем при помощи специального транслятора он получает готовый С++ код, реализующий интерфейс работы с базой данных, определенный пользователем, включая саму базу, ее таблицы, записи данных, транзакции и некоторые другие объекты. Код, генерируемый транслятором, на самом деле, является тоже оболочкой. Дело в том, что многие части транслятора имеют под собой общее основание. Эти статические классы и функции можно выделить в библиотеку. Еще одна причина для этого — семантика самого транслятора должна быть как можно проще. И как следствие этого, сокращаются размеры генерируемых файлов.

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

Новая база данных должна располагать такими возможностями:

· Добавление пользовательских данных их модификация и удаление.

· Открытие базы в нескольких режимах: например, в нормальном многопользовательском транзакционном режиме, безопасном режиме (как правило, для монопольного доступа и используется утилитами), а также в режиме восстановления базы данных.

· Импорта данных, то есть, представления данных в некотором текстовом формате, и их перемещение в пустую базу данных

· Экспорта данных, то есть, перемещение данных из базы, и представление их в определенном текстовом формате в файле, удобном для чтения. Является взаимно обратной операцией предыдущей.

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

· Проверки ссылочной целостности. То есть проверка корректности логических зависимостей между таблицами в базе.

Итак, вся оболочка состоит из следующих частей:

1. Собственно, базовая библиотека статических классов, для высокоуровневой работы с Berkeley, необходимая транслятору.

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

Библиотека классов

«Движок» представляет собой библиотеку классов, которые с одной стороны являются надстройками вокруг стандартных соответствующих структур, а с другой стороны делают их интерфейс более удобным и инкапсулируют часть работы транслятора. Основными компонентами являются:

· Транзакции

· Исключения

· Базовые записи

· Таблицы

· Базы данных

· Курсоры

Базовые записи

Базовая запись — это элементарная единица хранения в таблице. Описание ее класса:

//! базовый класс для записей с vtable pointer

class hbObj{

Dbt dbt;

protected:

void dbtInit (uint s, void* d)

{

dbt. set_flags (DB_DBT_USERMEM);

dbt. set_ulen (s);

dbt. set_size (s);

dbt. set_data (d);

}

public:

operator Dbt*(){return & dbt;}

void* getData (void) {return dbt. get_data ();};

uint getSize (void) {return dbt. get_size ();};

hbObj () {}

virtual ~hbObj () {}

};

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

//! реальный класс, который приводится к Dbt

template < class A> class hbRec: public hbObj

{

A data;

public:

A* getPnt (void) { return & data;} // если в в A массив то можно переопределить операцию & для А

hbRec () { memset (& data, 0, sizeof (A));dbtInit (sizeof (A),&data);}

hbRec (const hbRec< A>& a): data (a. data) { dbtInit (sizeof (A),& data);}

hbRec (const A& a): data (a) { dbtInit (sizeof (A),& data);}

void SetData (const A& a) { data = a; dbtInit (sizeof (A),&data);}

virtual ~hbRec () {}

};

Таблицы

Диаграмма отношений существующих таблиц приведена ниже:

57

По аналогии с записями существует базовый класс таблиц hbBasetbl, который поддерживает работу со всеми стандартными типами таблиц (Hash, Btree, Queue). Фактически ее тип является ее состоянием и определяется в момент открытия.

class hbBasetbl

{

// нужен для того чтобы set_flags вызывалась ровно один раз

uint Set_flg_Counter;

ushort state;

// флаг, показывающ. открыта ли сама таблица, необходим для экстр. закрытия в случае некоректного

// открытия

bool tableopen;

hbInit ini;

protected:

uint recsize;

uint keysize; //только для DB_HASH

Db *table;

virtual void UsrOpen (hbTxn *tx, FileConf& conf, bool openidx, hbInitRt* irt = 0, u_int32_t op_flags = 0);

virtual void UsrClose ();

void SetRecSize (uint recsize1){recsize = recsize1; }

void SetKeySize (uint keysize1){keysize = keysize1; }

uint GetType () {return ini. type;}

bool IsDup () ini. st_flags & DB_DUPSORT)> 0;

public:

hbEnv& env;

operator Db*(){return table; }

Db* operator -> (){return table; }

const char* GetDbName (){return ini. dbname;}

hbBasetbl (hbEnv& e, hbInit&);

virtual ~hbBasetbl (){ if (state) Close (); }

void Open (hbTxn *tx, FileConf& conf, bool openidx, hbInitRt* irt = 0, u_int32_t op_flags = 0);

void Close ();

virtual void Create (hbTxn *tx, FileConf& conf, hbInitRt* irt = 0, u_int32_t op_flags = 0);

virtual int Get (hbTxn *tx, hbObj *key, hbObj *val, u_int32_t flags=0); // в стиле С (без исключений)

virtual int Pget (hbTxn *tx, hbObj *fkey, hbObj *pkey, // в стиле С (без исключений)

hbObj *val, u_int32_t flags=0);

virtual int Del (hbTxn *tx, hbObj *key, u_int32_t flags=0); // в стиле С (без исключений)

virtual int tGet (hbTxn *tx, hbObj *key, hbObj *val, u_int32_t flags=0); // в стиле С++

virtual int tPget (hbTxn *tx, hbObj *fkey, hbObj *pkey, hbObj *val, u_int32_t flags=0); // в стиле С++

virtual int tDel (hbTxn *tx, hbObj *key, u_int32_t flags=0); // в стиле С++

virtual int Put (hbTxn *tx, hbObj *key, hbObj *val, u_int32_t flags=0);

bool IsOpen (){return state; }

};

Для ускорения доступа по какому-то критерию к данным в таблицах вводятся индексные таблицы. Ими могут быть любые из перечисленных, конечно в соответствии с их особенностями. Класс hbBasetbl является с одной стороны базовым классом, содержащим всю рутинную работу с флагами и основными операциями с таблицей, а с другой стороны -финальным классом для индексной таблицы.

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

class hbPTable: public hbBasetbl{

void ErrorClose ();

void eee ();

void FixIdx (uint bulk_ret_buffer_size, int i, FileConf& conf);

void FixIdxForQueue (uint bulk_ret_buffer_size, int i, FileConf& conf);

void FixIdxForHash (uint bulk_ret_buffer_size, int i, FileConf& conf);

void CheckMainToIdx (uint bulk_ret_buffer_size, bool fix, FileConf& conf);

void CheckMainToIdxForQueue (uint bulk_ret_buffer_size, bool fix, FileConf& conf);

void CheckMainToIdxForHash (uint bulk_ret_buffer_size, bool fix, FileConf& conf);

void CheckIdxToMain (uint bulk_ret_buffer_size, bool fix, FileConf& conf);

void CheckIdxToMainForQueue (uint bulk_ret_buffer_size, bool fix, FileConf& conf);

void CheckIdxToMainForHash (uint bulk_ret_buffer_size, bool fix, FileConf& conf);

inline void ExportForQueue (uint bulk_ret_buffer_size, FILE* f, hbTxn* tx);

inline void ExportForHash (uint bulk_ret_buffer_size, FILE* f, hbTxn* tx);

inline void Import3(Dbt* key, Dbt* data);

inline void Import2(char* buf);

inline void Import1(FILE* f, char*& buf1, uint&);

inline void CheckForRefForQueue (uint bulk_ret_buffer_size);

inline void CheckForRefForHash (uint bulk_ret_buffer_size);

inline uint GetMaxRefRecBuf ();

protected:

int sz;

IdxItem *idx;

RefItems ref;

virtual void UsrOpen (hbTxn *tx, FileConf& conf, bool openidx, hbInitRt* irt = 0, u_int32_t flags = 0);

virtual void UsrClose ();

inline virtual void ExportDBTemplate (FILE*, const char*, const char*) = 0;

inline virtual void ImportDBTemplate (char* buf1,

uint buf1len,

char* buf2,

uint buf2len,

hbObj*& Key,

hbObj*& Val) = 0;

public:

//! конструктор принимает массив инициализаторов (в тч индексов)

hbPTable (hbEnv& env, hbInit& ini1);

virtual ~hbPTable ();

// проверка индексной целостности

void CheckIdx (uint bulk_ret_buffer_size, bool fix);

// проверка ссылочной целостности

void CheckForRef (uint bulk_ret_buffer_size);

void Export (uint bulk_ret_buffer_size, FILE* f, hbTxn* tx);

void Import (FILE* f, char*& buf, uint&);

virtual int Pget (hbTxn *tx, int n, hbObj *fkey, hbObj* pkey, hbObj *val, u_int32_t flags=0)

{return idx[n]. table. Pget (tx, fkey, pkey, val, flags);}

hbBasetbl& GetIdx (int n)

{return idx[n]. table;}

inline uint GetIdxCount () {return sz; }

inline uint GetRecSize () {return recsize; }

};

Как видим, этот класс расширяет старый интерфейс путем введения утилитарных методов экспорта, импорта, различного рода проверок и операциями с индексными таблицами. Однако этот класс также не удобен в работе, так как не знает ничего о типах структур и ее характеристиках. Введение этих типов как параметров шаблона позволило бы очень упростить работу с интерфейсом индексируемой таблицы (но не расширить!). Результат приведен ниже:

template< class Key, class Val> class hbTable: public hbPTable

{

public:

//! конструктор принимает массив инициализаторов (в тч индексов)

hbTable (hbEnv& e, hbInit& ini1): hbPTable (e, ini1) {SetRecSize (sizeof (Val)); SetKeySize (sizeof (Key));}

//SetRecSize use by QUEUE only

virtual ~hbTable () {}

// более продвинутые функции

int Get (const bexcp& excp, hbTxn *tx, const Key & key, Val *val, u_int32_t flags=0)

{

Get (excp, tx,(Key*)& key, val, flags);

}

int Pget (const bexcp& excp, hbTxn *tx, int n, hbObj *fkey, Key *pkey, Val *val, u_int32_t flags=0)

{

MTRY

hbRec< Key> k;

hbRec< Val> v;

int z=Pget (tx, n, fkey,& k,&v, flags);

*pkey= *(k. getPnt ());

*val= *(v. getPnt ());

return z;

CATCH_hbExcp

}

int Del (const bexcp& excp, hbTxn *tx, const Key & key, u_int32_t flags=0)

{

Del (excp, tx,(Key*)& key, flags);

}

int tGet (const bexcp& excp, hbTxn *tx, Key *key, Val *val, u_int32_t flags=0)

{

MTRY

hbRec< Key> k (*key);

hbRec< Val> v;

int z = tGet (tx,& k,&v, flags);

*val= *(v. getPnt ());

return z;

CATCH_hbExcp

}

int Put (const bexcp& excp, hbTxn *tx, const Key & key, const Val & val, u_int32_t flags=0)

{

Put (excp, tx,(Key*)& key,(Val*)&val, flags);

}

uint Append (const bexcp& excp, hbTxn *tx, Val *val)

{

MTRY

if (GetType () ≠ DB_QUEUE) return 0;

hbRec< uint> k;

hbRec< Val> v (*val);

hbBasetbl: :Put (tx,&k,&v, DB_APPEND);

return (uint&)*(k. getPnt ());

CATCH_hbExcp

}

uint Append (const bexcp& excp, hbTxn *tx, const Val & val)

{

return Append (excp, tx,(Val*)& val);

}

};

Этот параметризированный класс на самом деле только переопределил сигнатуры методов более удобными и работающими с пользовательскими типами данных.

База данных или объект окружения

Этот объект фактически представляет собой абстракцию базы данных: является контейнером для индексируемых таблиц, отвечает за их открытие, доступ, а также проводит над ними служебные операции экспорта и т. п. На диаграмме это выглядит так:

57

Описание класса приведено ниже:

class hbEnv

{

DbEnv *env;

bool is_native_log;

Log* LogObj;

//! Путь к файлам

char* path;

//! Количество баз — ломает вектором писать — тогда дольше компилится

int sz;

//! Инишиалайзеры (в количестве равном sz)

hbInit *dbinits;

hbInit *idxinits;

int idxsz;

char* schemaid; // уже проверяется, при открытии из словаря чит. оригин. и сравнивается

//! Мутекс для транзакций

pthread_mutex_t mx;

uint dltype;

//! in secs interval for checkpoints and logarchs

ulong dldelay, bdbchkpoint, bdblogrem;

static void* thf_deadrs (void*);

static void* thf_chkpnt (void*);

static void* thf_logarc (void*);

pthread_t pth_deadrs, pth_chkpnt, pth_logarc;

ushort stflags;

ushort stflags;

bool IsOpenflag;

ushort state;

TDictionary dict;

//char* ConfFile; //имя конф. файла может переопределятся в потомках/ но зачем

FILE* OpenOutputStream (const char* fn, const char* mode);

void CloseOutputStream (FILE* f);

// удаляет все __db. 00x файлы т. к там хранится хэш, из-за которого может неверно сработать проверка индексов

protected:

//! Сами тейблы, индексов здесь нет, они в самих тейблах

hbPTable **dbs;

void SetSchemaid (const char* File) {if (schemaid)free (schemaid); schemaid = strdup (File); }

// тэйблы будут создаваться в конструкторе потомка, и вноситься в dbs

int Close (int);

virtual void UsrClose ();

public:

Log* GetLog () {return LogObj; }

operator DbEnv*() {return env; };

DbEnv* operator -> () {return env; }

//DbEnv& GetDbEnv (){ return env; }

const char* GetSchemaId ()const {return schemaid; }

const char* GetUuid (const bexcp&, hbTxn *tx);

const char* GetPath ()const {return path; }

bool IsOpen () {return state; }

hbEnv (const char *p, envInit& e, ushort flt = LL_DEBUG, Log* LogObj1 = 0);

virtual ~hbEnv ();

//st_flags помещ. в DbEnv: :set_flags (DB_TXN_NOSYNC)

//op_flags помещ. в Db: :open (DB_PRIVATE/DB_THREAD — by default)

// если режим CDS то эти флаги игнорируются за исключением op_flags = DB_PRIVATE!

void OpenTDSMode (const bexcp& excp, u_int32_t st_flags = 0, u_int32_t op_flags = (DB_THREAD | DB_RECOVER)) //DB_THREAD | DB_RECOVER_FATAL

{DBOpen (excp, OPEN_TDS, true, st_flags, op_flags); }

void OpenCDSMode (const bexcp& excp, bool opentables = true, u_int32_t op_flags = 0/*только для DB_PRIVATE*/)

{DBOpen (excp, OPEN_CDS, opentables, 0, op_flags); }

void Close (const bexcp& excp);

void Close ();

// полная инициализация& создание базы с нуля (предварительное удаление БД)

void Init (const bexcp& excp, u_int32_t op_flags=DB_THREAD);

// Проверка индексов и если надо их корректировка база должна быть в offline

void CheckForIdx (const bexcp& excp, uint bulk_ret_buffer_size = (5 * 1024 * 1024), bool fix = false);

void CheckForRef (const bexcp& excp, uint bulk_ret_buffer_size = (5 * 1024 * 1024));

//! экспорт базы даных

void ExportDB (const bexcp& excp, const char* fn, uint bulk_ret_buffer_size = (5 * 1024 * 1024));

//! импорт базы даных

void ImportDB (const bexcp& excp, const char* fn);

void printf (ushort level, const char* fmt,…); // обвертка под Log: :printf

};

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

Транзакции

Класс транзакций имеет следующий вид:

class hbTxn{

hbEnv& Env;

bexcp excp1;

hbTxn* parent;

DbTxn* Txn;

void SetFlags (){}

hbTxn (const hbTxn& Txn1): Env (Txn1. Env){} //copy constr

hbTxn& operator=(const hbTxn&){return *this;} //: =

public:

operator DbTxn*() {return Txn; };

hbTxn (const bexcp& excp, hbEnv& env1, ullong flags = 0, hbTxn* parent1 = 0); // младшие 32 бита это //обычн. беркл. флаги 33 бит отвечает за немедленный старт транзакции сразу же после создания

hbTxn (const bexcp& excp, hbTxn* parent1, ullong flags = 0);

// --- «---

~hbTxn ();

bool HaveParentTxn () {return parent≠0; }

void Start (const bexcp& excp, ulong flags = 0);

void Commit (const bexcp& excp, ulong flags = 0);

void RollBack (const bexcp& excp);

//void RollBack ();

};

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

try

{

hbTxn tx (excp, parent_tx);

// операции с базой

tx. Commit ();

}

catch (…){}
Как видим, первое — не надо заботиться об удалении объекта транзакции (при любой ситуации), второе — в случае исключения Rollback () вызовется автоматически в деструкторе этого объекта.

Транслятор

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

Файл грамматики приведен ниже:

%%

#-------------------------------------------------------

#------ COMMENTS --------------------------------------

#-------------------------------------------------------

#id идентификатор

#string строковый литерал или идентификатор

#num чиловой литерал

#float литерал числа с плавающей точкой

#char символьный литерал

#rawcode: := любая последователность кода между '{*' и '*}'

file: 'end' {tblproc: :Finish ();}

| filetext 'end' {tblproc: :Finish ();}

;

filetext: item

| item filetext

;

item: optionblock

| idxblock

| structblock

| enumblock

| codeblock

| tableblock

;

literal: string {[$_[1], 0]}

| num {[$_[1], 1]}

| float {[$_[1], 2]}

| char {[$_[1], 3]}

;

#---------------------------------------------------------

optionblock: 'option' '{' oplist '}' '; '

{tblproc: :OptBlockPrint ('',$_[3]);}

| 'option' opitem '; '

{tblproc: :OptBlockPrint ('',[$_[2]]);}

| 'option' id '{' oplist '}' '; '

{tblproc: :OptBlockPrint ($_[2],$_[4]);}

| 'option' id opitem '; '

{tblproc: :OptBlockPrint ($_[2],[$_[3]]);}

;

oplist:

opitem ';' {[$_[1]]}

| opitem ';' oplist {push @{$_[3]}, $_[1]; $_[3]}

;

opitem: id '=' literal

{[$_[1],@{$_[3]}[0], 0,@{$_[3]}[1]]}

| id '=' id {[$_[1],$_[3], 1,'']}

;

#---------------------------------------------------------

idxblock: 'idx' id idxitem '; '

{tblproc: :IdxBlockPrint ($_[2],[$_[3]]);}

| 'idx' id '{' idxitemlist '}' '; '

{tblproc: :IdxBlockPrint ($_[2],$_[4]);}

;

idxitemlist: idxitem ';' {[$_[1]]}

| idxitem ';' idxitemlist {unshift @{$_[3]},$_[1]; $_[3]}

;

idxitem: idxmod1 id '(' flist1 ')'

{[0,$_[1],$_[2],$_[4],'']}

| idxmod1 id '(' flist1 ')' '{*' rawcode '*}'

{[0,$_[1],$_[2],$_[4],$_[7]]}

| idxmod2 id '(' flist2 ')' {[1,$_[1],$_[2],$_[4],'']}

| idxmod2 id '(' flist2 ')' '{*' rawcode '*}'

{[1,$_[1],$_[2],$_[4],$_[7]]}

;

idxmod1: '. !'

| ': !'

| '%!'

;

idxmod2: '. '

| ': '

| '%'

;

flist1: id {[[$_[1],'']]}

| id ',' flist1 {unshift

@{$_[3]},[$_[1],'']; $_[3]}

;

flist2: idxmod3 id {[[$_[2],$_[1]]]}

| idxmod3 id ',' flist2 {unshift

@{$_[4]},[$_[2],$_[1]]; $_[4]}

;

idxmod3: '+'

| '-'

| '^'

;

#---------------------------------------------------------

codeblock: code 'decl' '{*' rawcode '*}'

{tblproc: :CodeBlockPrint ('hh', $_[4], 0); }

| code 'tab' '{*' rawcode '*}'

{tblproc: :CodeBlockPrint ('tab', $_[4], 0); }

| code 'def' '{*' rawcode '*}'

{tblproc: :CodeBlockPrint ('cc', $_[4], 0); }

| code 'def' 'top' '{*' rawcode '*}'

{tblproc: :CodeBlockPrint ('cc', $_[5], 1); }

| code '{*' rawcode '*}'

{tblproc: :CodeBlockPrint ('all', $_[3], 0); }

;

#---------------------------------------------------------

enumblock: 'enum' id '{' enumlist '}' '; '

{tblproc: :EnumBlockPrint ($_[2],$_[4]);}

| 'enum' id '; '

{tblproc: :EnumBlockPrint ($_[2],[]);}

;

enumlist: enumitem {[$_[1]]}

| enumitem ',' enumlist {unshift @{$_[3]}, $_[1]; $_[3]}

;

enumitem: id {[$_[1],'']}

| id '=' num {[$_[1],$_[3]]}

;

#---------------------------------------------------------

structblock: 'struct' id '{' structlist '}' '; '

{tblproc: :StructBlockPrint ($_[2],$_[4]);}

;

structlist: structitem {[$_[1]]}

| structitem structlist {unshift @{$_[2]}, $_[1]; $_[2]}

;

structitem: id pnlistid '; '

{[$_[1],@{$_[2]}[0],@{$_[2]}[1]]}

;

#---------------------------------------------------------

tableblock: tableforward

{tblproc: :TableBlockPrint (@{$_[1]}[0],'',[],[]);}

| tablehead '; '

{tblproc: :TableBlockPrint (@{$_[1]}[0],@{$_[1]}[1],@{$_[1]}[2], []); }

| tablehead tail '; '

{tblproc: :TableBlockPrint (@{$_[1]}[0],@{$_[1]}[1],@{$_[1]}[2], $_[2]); }

;

tail: idxtailitem {$_[1]}

| idxtailitem tail {unshift @{$_[2]},$_[1]; $_[2]}

| optiontailitem

| optiontailitem tail

;

tableforward: 'table' id ';' {[$_[2]]}

;

tablehead: 'table' memmodifid '{' memberlist '}'

{[@{$_[2]}[0],@{$_[2]}[1],$_[4]]}

;

memmodifid: id {[$_[1],'']}

| memmodificator id {[$_[2],'$']}

;

memberlist: memberitem {[$_[1]]}

| memberitem memberlist {unshift @{$_[2]}, $_[1]; $_[2]}

;

memberitem: id pnlistid '; '

{[$_[1],@{$_[2]}[0],@{$_[2]}[1],[]]}

| id pnlistid modificator1 '; '

{[$_[1],@{$_[2]}[0],@{$_[2]}[1],$_[3]]}

;

modificator1: idxmodificator {[$_[1], '']}

# | idxmodificator memmodificator {[$_[1], '$','']}

| idxmodificator '{*' rawcode '*}' {[$_[1], $_[3]]}

# | idxmodificator memmodificator '{*' rawcode '*}' {[$_[1], '$', $_[4]]}

;

pnlistid: pnlist id {[$_[1], $_[2]]}

| id {[[], $_[1]]}

;

pnlist: pointer {[$_[1],'']}

| pointer array {[$_[1],$_[2]]}

| array {['',$_[1]]}

;

pointer: '+'

| '-'

| '*'

;

array: '[' id ']' {$_[2]}

| '[' num ']' {$_[2]}

;

idxmodificator: '. !'

| ': !'

| '%!'

| '. +'

| ': +'

| '%+'

| '. -'

| ': -'

| '%-'

| '. ^'

| ': ^'

ПоказатьСвернуть
Заполнить форму текущей работой