Delete from mytable; Commit:.. или Восстановление данных в СУБД Interbase
Вадим Павлов,
Случилось страшное, кровь матери пролил сын обезумевший.
(c)Кто-то древний
Дисковая структура базы данных Interbase
Для понимания процесса восстановления и возможности такового изложим немного теории.
База данных состоит из одного или нескольких файлов. Файл базы данных, в свою очередь, состоит из набора <страниц> (далее просто страниц) фиксированной длины, размер которых указывается при создании базы данных, каждый файл базы данных содержит в себе страницы одинакового размера. Стандартной является страница 4096 байтов. Достоверность данного утверждения можно проверить, поделив размер файла на размер страницы. У Вас получится количество страниц содержащихся в базе. Для удобства работы с каждой страницей в отдельности можно разрезать данный файл на множество файлов содержащих в себе только одну станицу, либо воспользоваться программой IBSurgeon Viewer .
В Interbase используется несколько видов страниц:
• Страница-заголовок (Header page)
• Страница указателей (Pointer page)
• Страница учета транзакций (Transaction inventory page)
• Страница распределения места ( Space inventory page )
• Страница генераторов ( Generator page )
• Страница основного индекса ( Index root page )
• Станица индексов (Index page)
• Станица данных (Data page)
Типы страниц определены в файле ods . h таким образом:
/* Page types */
#define pag_undefined 0
#define pag_header 1 /* Database header page */
#define pag_pages 2 /* Page inventory page */
#define pag_transactions 3 /* Transaction inventory page */
#define pag_pointer 4 /* Pointer page */
#define pag_data 5 /* Data page */
#define pag_root 6 /* Index root page */
#define pag_index 7 /* Index (B-tree) page */
#define pag_blob 8 /* Blob data page */
#define pag_ids 9 /* Gen-ids */
#define pag_log 10 /* Write ahead log information */
#define pag_max 10 /* Max page type */
Страница заголовок (Header page) это первый блок в первом файле в базе данных. Когда Interbase присоединяется к базе данных он считывает первый килобайт файла. Заголовок содержит в себе критичную информацию о базе данных. Она включает в себя номер версии дисковой структуры (On Disk Structure - ODS) и размер страницы. После того, как сервер Interbase установил, что версия дисковой структуры, которая используется в файле им поддерживается, он перечитывает страницу-заголовок, используя правильный размер страницы и узнает необходимую информацию, такую как имена и диапазон страниц, содержащихся во вторичных файлах базы данных, следующую доступную транзакцию и последнюю, интересующую нас, транзакцию.
На последующем шаге ищется ядро системных таблиц и строится внутреннее представление базы данных.
Концептуализация
Итак, всё что можно было сделать плохого мы уже сами своими собственными руками сделали. Но не стоит сильно отчаиваться, в некоторых случаях можно помочь беде.
Много было уже рассказано и написано о том, как полезен бэкап и насколько часто его стоит делать. Но внештатные ситуации бывают всегда, и восстановление целого рабочего дня небольшой фирмы может оказаться трудоёмким и затруднительным процессом. Поэтому стоит немного подумать и посмотреть, а не проще ли попробовать восстановить удаленные данные.
Поиск в интернет, о возможности восстановления данных, не дал нам никаких результатов, поэтому будем делать всё сами.
Мы в данной статье будем рассматривать возможность восстановления данных в СУБД Interbase 6. x , Firebird 1. x для Windows (для других операционных систем возможны небольшие отличия в дисковой структуре).
Сначала немного формализуем задачу.
Что мы имеем:
1. Реляционную базу данных с несколькими тысячами записей, которые необходимо восстановить.
2. Устойчивое желание восстановить базу и немного терпения.
Как вам, наверное, уже известно, сразу после удаления какой либо информации из базы данных Interbase , файлы базы не уменьшаются, значит, вся информация осталась, а были только удалены ссылки на место хранения необходимой нам информации (записей).
После удаления информации о местоположении записей оказывается так, что утерянная информация находится в файле вперемешку с <мусором>. Тем более что, каждой записи может содержаться несколько версий.
Самое главное для успешного восстановления - быстрота вывода базы данных из эксплуатации. Чем раньше это будет сделано, тем выше вероятность восстановления утерянной информации.
Не нужно останавливать офис на неопределенный срок, достаточно просто скопировать базу поближе к себе и приступить к работе.
Для удобства можно воспользоваться утилитой IBSurgeon Viewer (http://www.ib-aid.com/), а также любым клиентом sql для Interbase , вполне подойдет ibconsole , wisql , isql и т.д. А так же нам очень помогут статьи The InterBase On - Disk Structure By Ann Harrison , Borland Developers Conference San Diego 2000, Structure of a Data Page By Paul Beach .
С задачей мы определились, установили необходимое программное обеспечение и запаслись статьями о структуре файла данных Interbase.
В данной статье мы рассмотрим все основные и необходимые моменты данных документов для восстановления информации из базы. Так же Вам придется применить навыки программирования, либо обратиться за помощью.
Итак, приступим.
Подробный формат страницы данных и записи
Рассмотрим формат страницы данных и записи более подробно.
Выборочно информацию возьмем из файла ods . h ( on disk structure ) из поставки Interbase ( firebird ) и опишем ее более подробно.
/* Page types */
#define pag_data 5 /* Data page */
Указывается, что страница данных идентифицируется номером 5.
#ifdef _CRAY
#define MIN_PAGE_SIZE 4096
#else
#define MIN_PAGE_SIZE 1024
#endif
#define MAX_PAGE_SIZE 16384
#define DEFAULT_PAGE_SIZE 4096
В данном листинге определяются размеры страницы: её максимальный и минимальный размер, а так же размер по умолчанию.
/* Basic page header */
Основной заголовок страницы
typedef struct pag {
SCHAR pag_type;
SCHAR pag_flags;
USHORT pag_checksum;
ULONG pag_generation;
ULONG pag_seqno; /*WAL seqno of last update*/
ULONG pag_offset; /*WAL offset of last update*/
} *PAG;
Определяется: тип страницы, флаги, контрольная сумма, номер последней измененной последовательности и смещение последнего изменения.
/* Data Page */
Определяется заголовок страницы данных
typedef struct dpg {
struct pag dpg_header;
SLONG dpg_sequence;
USHORT dpg_relation;
USHORT dpg_count;
struct dpg_repeat
{
USHORT dpg_offset;
USHORT dpg_length;
} dpg_rpt [1];
} *DPG;
Сначала идет стандартный заголовок страницы, далее следует номер последовательности в отношении, номер отношения, количество записей на странице. Далее следует повторяющаяся структура { смещение, длина } фрагмента записи.
/* Record header */
Заголовок записи
typedef struct rhd {
SLONG rhd_transaction;/* transaction id */
SLONG rhd_b_page; /* back pointer */
USHORT rhd_b_line; /* back line */
USHORT rhd_flags; /* flags, etc */
UCHAR rhd_format; /* format version */
#ifdef _CRAY
UCHAR rhd_pad [7];
#endif
UCHAR rhd_data [1];
} *RHD;
В заголовке записи определен номер транзакции, указатель на старую версию записи, флаги, версию формата записи и непосредственно данные.
Так же из файла ods . h можно узнать структуру фрагментированных записей и структуру записей поля blob . Но в данной статье мы не будем их рассматривать.
Далее определяются флаги записей
# define rhd _ deleted 1
Запись логически удалена
#define rhd_chain 2
Запись является старой версией
#define rhd_fragment 4
Запись является фрагментом
#define rhd_incomplete 8
Запись неполная
#define rhd_blob 16
Поле типа blob
#define rhd_delta 32
Предыдущая версия, различия только
#define rhd_large 64
Объект является большим
#define rhd_damaged 128
Объект известен , как поврежденный
#define rhd_gc_active 256
Мусор, мертвая версия записи
Как говорилось, ранее данные в записях сжаты методом кодирования переменной длины ( RLE ). Суть метода заключается в том, что положительное число указывает на количество следующих байт, которые непосредственно следует прочитать, отрицательное же число указывает, что следующий байт нужно повторить abs ( n ) (где abs - модуль числа) раз.
Заголовок хранит информацию о транзакции, которая требуется для реализации multi - generational архитектуры, в нормальной записи заголовок будет только содержать указатель на старую версию.
После заголовка и перед данными идет вектор нулевых бит на каждое поле (добитое до 8 (битовой) байтовой границы) в таблице. Если флаг установлен, тогда поле равно нулю, пока данные установлены в 0 для улучшения компрессии. Это означает что заголовок состоит из эффективной длины 16 байт и 3 байта были добавлены для выравнивания. Байты для нулевого битового вектора добавляются по необходимости, в зависимости от количества полей, определенных в таблице.
В результате того, что резервируется место для версий, когда заканчивается место на странице, остается возможность того, что новая версия записи будет сохранена на этой же странице.
При восстановлении данных не следует забывать о внутреннем формате представления чисел, даты и времени и кодировке строк, дабы заранее не разочароваться в возможности восстановления.
Напомню вам, что на платформе intel при записи числа типа word (два байта), сначала записывается младший байт, а затем старший, например число 0 x 5684 будет представлено на диске, как 84:56. Аналогично сохраняются и числа типа long .
Формат даты является необычным по сравнению со стандартными типами записи дат, поэтому немного остановимся на его описании.
Для хранения даты и времени в Interbase существует тип date, его внутреннее представление таково. Это запись из двух 32-разрядных знаковых целых чисел. В первом числе хранится число дней, прошедших с 17 ноября 1858, а во втором - время в десятых долях миллисекунды, прошедшее после полуночи.
Для перевода в стандартный тип Delphi - TDateTime, который объявлен как TDateTime = type Double, где целая часть - это число дней, прошедших с 30 декабря 1899, а дробная часть - время, прошедшее после полуночи (.0 = 0:00; .25 = 6:00; .5 = 12:00; .75 = 18:00) можно воспользоваться простой формулой
DateTime := Days - IBDateDelta + MSec10 / MSecsPerDay10, где
Days - количество дней в формате Interbase ;
IBDateDelta = 15018 - разница в днях между датами Delphi и Interbase;
MSec 10 - время в десятых долях миллисекунды, прошедшее после полуночи;
MSecsPerDay10 = Количество миллисекунд в сутках * 10
На данный момент мы в достаточной степени узнали необходимую информацию о том, каким образом и где хранится информация в препарируемой нами базе данных. Изучив подробно все структуры используемые СУБД было бы возможно написать программу и воочию посмотреть и восстановить все данные своими руками. Но, как говорится, <Кесарю - кесарево>, я же предпочитаю воспользоваться стандартной программной isq ( wisq , кому как нравится) и движком interbase (ведь не зря его писали?) если, конечно база данных не повреждена, и тем самым ускорить процесс восстановления информации.
Данная статья написана на основе
Данная статья написана на основе статей: "The Interbase On - Disk Structure" by Ann Harrison ; "Structure of a Data Page" by Paul Beach, а так же включает в себя мой горький опыт в удалении важных данных, а потом в их успешном восстановлении. Я никоим образом не собираюсь нарушать права ни выше перечисленных авторов, ни компаний, чьи зарегистрированные торговые знаки перечисленные в данной статье.
Эта статья была написана Вадимом Павловым в 2004г. с соответствующим наложением авторских прав. Вы можете опубликовать её по своему усмотрению, единственным условием является опубликование данного абзаца.
По любым вопросам, касающимся данной статьи можно связаться с автором по адресу: pvm _ job(at)mail.ru, либо пообщаться на форуме, в чате сайта penguin.photon.ru
SQL- запросы
Далее мы рассмотрим sql -запросы, которые необходимы нам для восстановления утерянной информации.
select rdb$relation_fields.rdb$field_name, rdb$relation_fields.rdb$field_id, rdb$fields.rdb$field_length, rdb$types.rdb$type_name from rdb$relation_fields left join rdb$fields on rdb$relation_fields.rdb$field_source= rdb$fields.rdb$field_name left join rdb$types on rdb$types.rdb$type=rdb$fields.rdb$field_type where (rdb$relation_name=' MYTABLE ') and (rdb$types.rdb$field_name= 'RDB$FIELD_TYPE')
Данный запрос дает нам ответ на один из главных вопросов, как расположены поля в записи таблицы MYTABLE , какой имеют размер и тип
Пример выборки :
RDB$
FIELD_NAME
RDB$
FIELD_ID
RDB$
FIELD_LENGTH
RDB$
TYPE_NAME
field 1
0
4
long
field2
1
2
float
field3
3
8
double
field4
4
1
varying
В первом столбце указано название поля, во втором его порядок в записи, в третьем длина поля в байтах, а в четвертом тип поля.
Осталось только узнать в каких страницах расположена таблица, которую мы пытаемся восстановить.
select rdb$relation_id,rdb$relation_name, RDB$PAGE_NUMBER, rdb$page_type from rdb$pages left join RDB$relations on rdb$pages. RDB$RELATION_ID=RDB$relations.RDB$RELATION_ID where rdb$relation_name=' MYTABLE ';
Пример выборки:
RDB$
RELATION_ID
RDB$
RELATION_NAME
RDB$
PAGE_NUMBER
RDB$
PAGE_TYPE
145
MYTABLE
198
4
145
MYTABLE
199
6
В первой колонке указан номер отношения (таблицы), во второй название таблицы, в третьей страницы, на которых расположена таблица, а в четвертой тип таблицы.
На самом деле, <увы и ах>, мы только <добыли> из базы номера страниц указателя и индекса, а не полностью список всех страниц. Так что, засучим рукава и будем работать далее. Страница индексов для нас никоем образом не важна, а формат страницы указателя был вкратце описан выше.
Остановимся на нем более подробно.
typedef struct ppg {
struct pag ppg_header;
SLONG ppg_sequence; /* Sequence number in relation */
SLONG ppg_next; /* Next pointer page in relation */
USHORT ppg_count; /* Number of slots active */
USHORT ppg_relation; /* Relation id */
USHORT ppg_min_space;/* Lowest slot with space available */
USHORT ppg_max_space;/* Highest slot with space available */
SLONG ppg_page [1]; /* Data page vector */ } *PPG;
#define ppg_eof 1 /* Last pointer page in relation */
В стандартном заголовке для нас особо интересно поле flag , если как описано выше, оно установлено в <1>, то это значит что таблица указателей является последней для рассматриваемого отношения (таблицы), и соответственно мы можем узнать все необходимые нам данные.
За стандартным заголовком страницы, идет заголовок страницы указателей, где соответственно указывается: номер данной страницы, в последовательности страниц указателя для данного отношения (таблицы); номер следующей страницы для данного отношения; количество активных слотов (то есть записей о том, какие страницы используются); номер отношения (таблицы); наименьший доступный слот и максимальный доступный слот.
Далее идет вектор данных, в котором непосредственно и перечислены страницы.
Всё вышеописанное можно наглядно посмотреть с помощью программы IBSurgeon Viewer .
Итак, что мы знаем в данный момент:
• структуру таблицы - каким образом расположены в ней поля и их размер;
• номера страниц данных, в которых находится наша таблица;
• структуру страниц данных.
Таким образом, у нас выполнены необходимые и достаточные условия, чтобы попробовать восстановить утерянные данные.
Страница BLOB ( Data page )
Страницы, полностью занятые двоичными данными ( BLOB ), можно при определённых условиях выделить в отдельный тип страниц БД, которые не отражаются на странице указателей.
Также страница двоичных данных может быть страницей указателей на другие страницы двоичных данных. Для каждого созданного поля BLOB создаётся запись, содержащая информацию о расположении данных поля и данные о содержимом, которые могут быть полезны при чтении. Механизм хранения таких полей определяется их размером и бывает трёх типов (0,1,2).
• Механизм 0 . Поле умещается на одной странице БД вместе с записью.
• Механизм 1 . Когда поле не умещается на одной странице БД вместе с записью, поле записывается в специализированные страницы, а в поле двоичных данных на странице с остальными полями данных записи будет помещен массив указателей на занятые полем страницы
• Механизм 2. Когда места на начальной странице не хватает даже для того, чтобы записать туда массив указателей, InterBase создаст страницу (страницы) указателей на страницы с полем BLOB.
Страница данных (Data page)
Страница данных содержит следующие данные: записи, фрагменты записей, фрагменты, запасные версии, изменения, поля типа blob и относящиеся к данному полю структуры. Заголовок страницы, в частности, содержит тип страницы, номер отношения (relation , таблицы) и номер следующей страницы, содержащую данную таблицу. В отличие от других страниц, страницы данных содержат значимую структуру, следующую сразу за заголовком. Эта структура имеет название индекс записей. Последняя часть db_key записи является смещением в массиве индексов записей снизу страницы. Этот индекс содержит актуальное положение и размер сохраненной на странице строки таблицы.
Строки индекса расположены после заголовка до конца страницы. Записи таблицы сохраняется снизу вверх. Когда они встречаются, страница объявляется полной. В случае с резервированием места для изменений (режим по умолчанию), страница будет заполнена до определенной точки, оставшееся место будет использовано для создания новых версий записей на текущей странице.
Эти структуры мы рассмотрим далее более подробно.
Индекс должен содержать длину сохраненной записи, несмотря на то, что все записи на странице принадлежат одной и той же таблице, поэтому, в теории должны быть одной длины. Дело в том, что записи компрессируются перед тем, как будут сохранены. Алгоритм сжатия самый простой - кодирование переменной длины, которое предназначено для отлова общих мест нулевых колонок и последовательных пробелов.
Индекс содержит смещение, поэтому запись может перемещаться по странице без воздействия на её основной идентификатор - её db_key. Когда Interbase обнаруживает, что страница стала фрагментированной в следствии того что некоторые записи были удалены, то он сдвигает все оставшиеся данные вместе, делая одно большое пространство вместо небольших маленьких фрагментов. Это обычные действия базы данных.
Как же выглядит строка таблицы на этом уровне? Во-первых, идет фиксированной длины заголовок, который включает в себя номер транзакции, которая создала эту версию записи и формат версии для этой записи. Если существует старая версия записи, то заголовок также содержит указатель на неё. В заголовке также указан тип записи: обычная запись, фрагментированная запись, фрагмент или поле blob . Маленькие blob -поля часто расположены на странице вместе с записью, к которой они относятся. Если запись фрагментирована, то в заголовке указывается на фрагмент.
Последняя часть служебных данных в записи - добавление к записи переменной длины битов (кратной 8), определяющие значения NULL в соответствующих полях.
Столбцы таблицы расположены в соответствии со значением rdb$field_id таблицы rdb$relation_fields, которая описывает их. Если порядок, определенный значением поля rdb$position отличается, то высокоуровневый механизм приводит их в соответствующий порядок. Высокоуровневая функция также смотрит на версию формата записи и использует ее для нахождения соответствующего формата в rdb$formats. Все записи будут переведены в наиболее подходящий формат во время перемещения из страницы в кэш записей. До тех пор, пока запись не будет изменена, она не будет перезаписана, даже если её формат устарел.
Если запись является сохраненной ( back version ), её основная версия будет содержать флаг, который показывает какая из них является изменением. Изменения - это набор различий, которые могут быть применены к основной записи для создания её сохраненной ( back ) версии. Формат изменения очень похож на кодирование переменной длины, с байтами указывающим количество заменяемых символов и количество сохраняемых символов.
Записи не фрагментируются до тех пор, пока сжатый размер данных меньше размера страницы. Когда запись модифицируется её сжатый размер может увеличиваться, до тех пор, пока не останется места на странице, в данном случае она будет фрагментироваться. Фрагментация значительно влияет на производительность, так как чтение фрагментированной записи требует выборку, как минимум двух страниц. Запись фрагментированных строк может требовать четыре или более страниц для использования стратегии <точной записи>, которая требует, чтобы изменения на странице были записаны в соответствующем порядке.
Страница генераторов ( Generator page )
Страница генераторов последняя из <простых> страниц. Страница генераторов состоит из заголовка и четырехбайтовых записей, которые представляют состояние генераторов. Индексом в этом массиве служит порядковый номер генератора. Таблица rdb$pages содержит в себе записи о страницах генераторов.
Страница индекса ( index page )
Заголовок страницы индекса включает тип страницы и номер страницы следующей на данном уровне.
Индексами называется древовидная структура. На странице основного индекса указывается верхняя страница индекса. Она содержит записи, указывающие на точки следующих уровней индексных страниц.
Страница основного индекса ( Index root page )
Любые таблицы, включая те, у которых нет индексов, имеют страницу основного индекса. Страница основного индекса отожествляется с вершиной каждого индекса определенного для таблицы. На этой странице должен быть описан (перечислены поля индексации) каждый индекс таблицы БД. Также здесь указывается полезность индекса, которая может быть рассчитана как отношение числа различающихся индексных полей внутри индекса и среднего количества записей. Среднее количество записей - это число страниц БД, занятых данной таблицей, делённой на максимальное количество записей на странице. Полезность индекса - чрезвычайно важный показатель, на основании которого InterBase оптимизирует выполнение запросов.
Страница распределения места ( Space inventory page )
Последней из страниц <хранителей очага> является страница распределения места. Страница распределения места показывает какие страницы являются распределенными (<зарезервированными>) и если она зарезервирована, то свободна ли она и насколько.
Страница распределения места находится сразу за страницей-заголовком. Она подобно всем страницам имеет заголовок, в котором определен тип страницы. Остальная часть страницы - массив 1-битных кластеров, который соответствует страницам в базе данных. Каждая страница вне зависимости от типа включена в страницу распределения пространства (за исключением страницы-заголовка). Заголовок страницы распределения места не включает в себя указатель на следующую страницу распределения места, так же эти страницы не перечислены в таблице rdb$pages . Они расположены на определенных интервалах. Таким образом, Interbase вычисляет расположение следующей страницы в зависимости от размера страницы базы данных и длины заголовка.
Когда страница добавляется в таблицу или индекс, Interbase меняет её состояние в странице распределения места. Ошибка "orphan page" возникает, когда сервер находится в середине процесса распределения новой страницы. Запись в страницу распределения места сделана, указанная страница выделена, но новая страница не была записана и осталась как <сирота>.
Страница учета транзакций (Transaction inventory page).
Таблица rdb$pages содержит записи не только о страницах данных, а также о страницах учета транзакций. Подобно странице указателей страница учета транзакций состоит из простого заголовка, который включает в себя тип страницы и номер следующей страницы учета транзакций. Оставшаяся часть страницы является массивом двухбитовых записей, которые отражают состояние транзакций в системе. Ноль указывает на то, что транзакция не была начата, активна или <погибла> без подтверждения или отката. Единица указывает на то, что транзакция была подтверждена. Двойка указывает на то, что транзакция была опровергнута. Тройка указывает на то, что транзакция в состоянии <лимбо> (неопределенное состояние, которое существует в середине двухфазового подтверждения транзакции).
Чтобы определить состояние транзакции Interbase использует номер транзакции, как индекс в массиве транзакций и просматривает состояние соответствующих битов. Алгоритм более сложный, так как зависит от заголовка страницы. В случае если одна транзакция проверяет состояние другой транзакции и обнаруживает, что она обозначена как активная, а фактически является <мертвой> - (<смерть>транзакции проверяется по средством проверки таблицы блокировки) - то она меняет состояние <мертвой> транзакции с активной на откаченную.
Страница указателей ( Pointer page )
Страница-заголовок содержит информацию о месте положения первой страницы указателей для таблицы rdb$pages . Системная таблица rdb$pages позволяет interbase найти критичные страницы, включающие страницы данных для таблиц. Система читает первую страницу указателей и использует её для поиска первой страницы данных rdb$pages . Из неё затем можно найти страницы данных и индексов других системных таблиц.
Таблица 1.
Записи системной таблицы rdb$pages
RDB$
PAGE_NUMBER
RDB$
RELATION_ID
RDB$
PAGE_SEQUENCE
RDB$
PAGE_TYPE
3
0
0
4
4
0
0
6
6
1
0
4
7
1
0
6
8
2
0
4
9
2
0
6
129
0
0
9
140
0
0
3
169
0
10
3
Страница указателей имеет простой заголовок, который включает в себя: тип страницы и номер следующей страницы указателей данной таблицы. Остальная часть страницы заполнена массивом из четырехбайтовых чисел, которые являются номерами страниц и составляют данную таблицу
Восстановление данных
И так мы можем приступать к самому ответственному этапу. Для удобства работы можно порезать файл базы данных на множество страниц и вычленить страницы с необходимой нам таблицей. Для начала пусть это будет любая таблица с текстовыми данными, например справочник товаров и т.д. Просмотрев его любым текстовым редактором можно заметить, что в данном файле находятся утерянные текстовые данные. Но возникает другой вопрос, как эти данные вытащить из базы, если заголовок со смещением и размером записи утерян навсегда. Возможно придумать массу алгоритмов с помощью которых можно восстановить утерянные данные, я предлагаю, на мой взгляд, самый простой, да и по вычислительным возможностям современных компьютеров восстановление происходит относительно быстро. В качестве примера данного утверждения могу сказать, что восстановление 2000 записей (не текстовая таблица) на Athlon XP 2000, программой написанной на Delphi , заняло порядка 10 секунд.
Суть алгоритма состоит в том, что нам известно следующее:
• размер записи в байтах;
• записи начинаются с конца страницы и сжаты методом rle ;
• записи расположены не по всей странице;
• на любые данные, находящиеся в таблице, можно наложить соответствующие ограничения.
Возьмем на рассмотрение самый простой метод, варьированием параметров которого можно добиваться увеличения производительности программы восстановления во много раз.
Начиная с конца страницы, берем несколько байт (для каждой таблицы можно отдельно посчитать необходимый минимум) и просто пытаемся раскодировать, если после декодирования у нас не получился размер равный размеру записи, то делаем шаг приращения (советую в один байт, пока не найден кандидат в запись, потом шаг можно пересмотреть), и повторяем процедуру заново.
В случае, если размеры совпали, то мы получили кандидата на правильную запись в первом приближении, однако, она должна удовлетворять многим условиям. Прежде всего, её заголовок не должен содержать ничего криминального, после этого проверяем на правильность данных, которые несет в себе запись.
При проверке стоит учитывать несколько факторов, которые напрямую зависят от предметной области базы данных:
• Если первичным ключом таблицы является поле с автоинкриментом, то соответственно оно не должно быть более чем значение генератора, так же, если не определено иное оно не может быть отрицательным;
• Даты, если это, например, простенькая складская программа, не могут быть в далёком прошлом и далёком будущем;
• Цена на товар не может быть отрицательной либо слишком большой;
Таким образом, кроме вышеперечисленных, можно выдумать ещё массу реальных ограничений, которые можно наложить на данные.
В качестве примера могу сказать, что при восстановлении 2000 записей имеющими дубликаты оказались только 4.
Так как их количество оказалось не сильно велико, то основываясь на знаниях, что именно должно было указано в этих полях, лично я не стал утруждать себя и возится с версиями записей.
В результате проделанной работы мы получаем из базы всё что нужно.
Для простоты восстановления можно просто приостановить работу всех триггеров и с помощью sql запросов добавить недостающие данные.
ГОТОВО!!!!
Восстановление информации
В самом простом случае для восстановления данных нам нужно знать лишь структуру страниц данных. Всю остальную полезную информацию мы сможем получить с помощью sql запросов к системным таблицам Interbase , что существенно ускорит и упростит процесс восстановления информации и, соответственно, продлит и укрепит наш сон.
Функции
Функции позволяют многократно наращивать функциональность InterBase сервера за счет подключения внешних модулей, выполненных в виде модулей dll.
Вот так можно получить список функций, зарегистрированных в базе данных.
SELECT RDB$FUNCTIONS.RDB$FUNCTION_NAME, RDB$FUNCTIONS.RDB$SYSTEM_FLAG FROM RDB$FUNCTIONS WHERE ( (RDB$FUNCTIONS.RDB$SYSTEM_FLAG = 0) or (RDB$FUNCTIONS.RDB$SYSTEM_FLAG IS NULL) );
Следующий запрос возвращает более полную информацию о функциях: наименование, описание, наименование модуля и точки входа.
SELECT RDB$FUNCTIONS.RDB$FUNCTION_NAME, RDB$FUNCTIONS.RDB$DESCRIPTION, RDB$FUNCTIONS.RDB$MODULE_NAME, RDB$FUNCTIONS.RDB$ENTRYPOINT, RDB$FUNCTIONS.RDB$RETURN_ARGUMENT FROM RDB$FUNCTIONS WHERE ( (RDB$FUNCTIONS.RDB$SYSTEM_FLAG = 0) or (RDB$FUNCTIONS.RDB$SYSTEM_FLAG IS NULL) );
Воспользовавшись информацией из таблицы RDB$FUNCTION_ARGUMENTS, можно получить сведения об аргументах функций.
SELECT RDB$FUNCTIONS.RDB$FUNCTION_NAME, RDB$FUNCTIONS.RDB$DESCRIPTION, RDB$FUNCTIONS.RDB$MODULE_NAME, RDB$FUNCTIONS.RDB$ENTRYPOINT, RDB$FUNCTIONS.RDB$RETURN_ARGUMENT, RDB$FUNCTION_ARGUMENTS.RDB$ARGUMENT_POSITION, RDB$FUNCTION_ARGUMENTS.RDB$MECHANISM, RDB$FUNCTION_ARGUMENTS.RDB$FIELD_TYPE, RDB$TYPES.RDB$TYPE_NAME, RDB$FUNCTION_ARGUMENTS.RDB$FIELD_LENGTH, RDB$FUNCTION_ARGUMENTS.RDB$FIELD_PRECISION FROM RDB$FUNCTIONS INNER JOIN RDB$FUNCTION_ARGUMENTS ON (RDB$FUNCTIONS.RDB$FUNCTION_NAME = RDB$FUNCTION_ARGUMENTS.RDB$FUNCTION_NAME) INNER JOIN RDB$TYPES ON (RDB$FUNCTION_ARGUMENTS.RDB$FIELD_TYPE = RDB$TYPES.RDB$TYPE) WHERE ( ((RDB$FUNCTIONS.RDB$SYSTEM_FLAG = 0) or (RDB$FUNCTIONS.RDB$SYSTEM_FLAG IS NULL)) and (RDB$TYPES.RDB$FIELD_NAME = 'RDB$FIELD_TYPE') ) ORDER BY RDB$FUNCTIONS.RDB$FUNCTION_NAME, RDB$FUNCTION_ARGUMENTS.RDB$ARGUMENT_POSITION;
Следующий пример запроса позволяет получить информацию только по выходным аргументам функций.
SELECT RDB$FUNCTIONS.RDB$FUNCTION_NAME, RDB$FUNCTIONS.RDB$DESCRIPTION, RDB$FUNCTIONS.RDB$MODULE_NAME, RDB$FUNCTIONS.RDB$ENTRYPOINT, RDB$FUNCTIONS.RDB$RETURN_ARGUMENT, RDB$FUNCTION_ARGUMENTS.RDB$ARGUMENT_POSITION, RDB$FUNCTION_ARGUMENTS.RDB$MECHANISM, RDB$FUNCTION_ARGUMENTS.RDB$FIELD_TYPE, RDB$TYPES.RDB$TYPE_NAME, RDB$FUNCTION_ARGUMENTS.RDB$FIELD_LENGTH, RDB$FUNCTION_ARGUMENTS.RDB$FIELD_PRECISION FROM RDB$FUNCTIONS INNER JOIN RDB$FUNCTION_ARGUMENTS ON (RDB$FUNCTIONS.RDB$FUNCTION_NAME = RDB$FUNCTION_ARGUMENTS.RDB$FUNCTION_NAME) INNER JOIN RDB$TYPES ON (RDB$FUNCTION_ARGUMENTS.RDB$FIELD_TYPE = RDB$TYPES.RDB$TYPE) WHERE ( ((RDB$FUNCTIONS.RDB$SYSTEM_FLAG = 0) or (RDB$FUNCTIONS.RDB$SYSTEM_FLAG IS NULL)) and (RDB$TYPES.RDB$FIELD_NAME = 'RDB$FIELD_TYPE') and (RDB$FUNCTIONS.RDB$RETURN_ARGUMENT = RDB$FUNCTION_ARGUMENTS.RDB$ARGUMENT_POSITION) ) ORDER BY RDB$FUNCTIONS.RDB$FUNCTION_NAME, RDB$FUNCTION_ARGUMENTS.RDB$ARGUMENT_POSITION;
Здесь же, наоборот, получаем информацию только о входных аргументах функций.
SELECT RDB$FUNCTIONS.RDB$FUNCTION_NAME, RDB$FUNCTIONS.RDB$DESCRIPTION, RDB$FUNCTIONS.RDB$MODULE_NAME, RDB$FUNCTIONS.RDB$ENTRYPOINT, RDB$FUNCTIONS.RDB$RETURN_ARGUMENT, RDB$FUNCTION_ARGUMENTS.RDB$ARGUMENT_POSITION, RDB$FUNCTION_ARGUMENTS.RDB$MECHANISM, RDB$FUNCTION_ARGUMENTS.RDB$FIELD_TYPE, RDB$TYPES.RDB$TYPE_NAME, RDB$FUNCTION_ARGUMENTS.RDB$FIELD_LENGTH, RDB$FUNCTION_ARGUMENTS.RDB$FIELD_PRECISION FROM RDB$FUNCTIONS INNER JOIN RDB$FUNCTION_ARGUMENTS ON (RDB$FUNCTIONS.RDB$FUNCTION_NAME = RDB$FUNCTION_ARGUMENTS.RDB$FUNCTION_NAME) INNER JOIN RDB$TYPES ON (RDB$FUNCTION_ARGUMENTS.RDB$FIELD_TYPE = RDB$TYPES.RDB$TYPE) WHERE ( ((RDB$FUNCTIONS.RDB$SYSTEM_FLAG = 0) or (RDB$FUNCTIONS.RDB$SYSTEM_FLAG IS NULL)) and (RDB$TYPES.RDB$FIELD_NAME = 'RDB$FIELD_TYPE') and (RDB$FUNCTIONS.RDB$RETURN_ARGUMENT <> RDB$FUNCTION_ARGUMENTS.RDB$ARGUMENT_POSITION) ) ORDER BY RDB$FUNCTIONS.RDB$FUNCTION_NAME, RDB$FUNCTION_ARGUMENTS.RDB$ARGUMENT_POSITION;
Генераторы
Генераторы позволяют гарантированно получать уникальные значения из какой-либо последовательности. Обычно это используется для заполнения ключевых полей.
Следующий запрос позволяет получить перечень генераторов, созданных пользователями.
SELECT RDB$GENERATORS.RDB$GENERATOR_NAME, RDB$GENERATORS.RDB$SYSTEM_FLAG FROM RDB$GENERATORS WHERE ((RDB$GENERATORS.RDB$SYSTEM_FLAG = 0) or (RDB$GENERATORS.RDB$SYSTEM_FLAG IS NULL ));
А так можно узнать текущее значение генератора (EMP_NO_GEN).
SELECT DISTINCT GEN_ID(EMP_NO_GEN, 0) FROM RDB$GENERATORS;
Индексы
Индексы являются важными объектами базы данных. Кроме того, что они обеспечивают некоторые виды ограничений, они позволяют ускорять доступ к данным и упорядочивать их.
Следующий запрос выбирает из системной таблицы RDB$INDICES индексы, созданные пользователями, с указанием таблиц, для которых они были определены.
SELECT RDB$INDICES.RDB$INDEX_NAME, RDB$INDICES.RDB$RELATION_NAME FROM RDB$INDICES WHERE (NOT (RDB$INDICES.RDB$INDEX_NAME STARTING WITH 'RDB$')) ORDER BY RDB$INDICES.RDB$RELATION_NAME;
Ниже приведен запрос, который выдает основную, наиболее интересную информацию об индексах. Для уменьшения объема выдаваемой информации запрос сделан для таблицы SALES.
SELECT RDB$INDICES.RDB$INDEX_NAME, RDB$INDICES.RDB$RELATION_NAME, RDB$INDICES.RDB$UNIQUE_FLAG, RDB$INDICES.RDB$DESCRIPTION, RDB$INDICES.RDB$SEGMENT_COUNT, RDB$INDICES.RDB$INDEX_INACTIVE, RDB$INDICES.RDB$INDEX_TYPE, RDB$INDICES.RDB$STATISTICS FROM RDB$INDICES WHERE ( NOT (RDB$INDICES.RDB$INDEX_NAME STARTING WITH 'RDB$') and (RDB$INDICES.RDB$RELATION_NAME = 'SALES') ) ORDER BY RDB$INDICES.RDB$RELATION_NAME;
Следующий запрос, кроме информации о самих индексах, выдает перечни полей таблиц, на которых построены индексы, а также порядок полей в индексе.
SELECT RDB$INDICES.RDB$INDEX_NAME, RDB$INDICES.RDB$RELATION_NAME, RDB$INDICES.RDB$UNIQUE_FLAG, RDB$INDICES.RDB$DESCRIPTION, RDB$INDICES.RDB$SEGMENT_COUNT, RDB$INDICES.RDB$INDEX_INACTIVE, RDB$INDICES.RDB$INDEX_TYPE, RDB$INDICES.RDB$STATISTICS, RDB$INDEX_SEGMENTS.RDB$FIELD_NAME, RDB$INDEX_SEGMENTS.RDB$FIELD_POSITION FROM RDB$INDICES INNER JOIN RDB$INDEX_SEGMENTS ON 0(RDB$INDICES.RDB$INDEX_NAME = RDB$INDEX_SEGMENTS.RDB$INDEX_NAME) WHERE ( NOT (RDB$INDICES.RDB$INDEX_NAME STARTING WITH 'RDB$') and (RDB$INDICES.RDB$RELATION_NAME = 'SALES') ) ORDER BY RDB$INDICES.RDB$INDEX_NAME, RDB$INDEX_SEGMENTS.RDB$FIELD_POSITION
Исключения
Исключения представляют собой созданные пользователем ошибки с сопоставленным текстом. Их можно использовать в теле триггеров или процедур при возникновении какой-либо ошибки.
Данный запрос позволяет получить перечень исключений с сообщениями, которые за ними закреплены.
SELECT RDB$EXCEPTIONS.RDB$EXCEPTION_NAME, RDB$EXCEPTIONS.RDB$EXCEPTION_NUMBER, RDB$EXCEPTIONS.RDB$MESSAGE, RDB$EXCEPTIONS.RDB$SYSTEM_FLAG FROM RDB$EXCEPTIONS WHERE ((RDB$EXCEPTIONS.RDB$SYSTEM_FLAG = 0) or (RDB$EXCEPTIONS.RDB$SYSTEM_FLAG IS NULL));
Ограничения
При создании базы данных на таблицы могут быть наложены различные ограничения, это и ограничения ссылочной целостности и всевозможные проверки вводимых значений и, к примеру, ограничения NOT NULL.
Перечень ограничений можно получить из таблицы RDB$RELATION_CONSTRAINTS. Приведенный пример запроса возвращает наименование ограничения, его тип, наименование таблицы, на которую данное ограничение распространяется и индекс, если ограничение базируется на индексе.
SELECT RDB$RELATION_CONSTRAINTS.RDB$CONSTRAINT_NAME, RDB$RELATION_CONSTRAINTS.RDB$CONSTRAINT_TYPE, RDB$RELATION_CONSTRAINTS.RDB$RELATION_NAME, RDB$RELATION_CONSTRAINTS.RDB$INDEX_NAME FROM RDB$RELATION_CONSTRAINTS ORDER BY RDB$RELATION_CONSTRAINTS.RDB$RELATION_NAME;
Следующий запрос возвращает CHECK ограничения.
SELECT RDB$RELATION_CONSTRAINTS.RDB$CONSTRAINT_NAME, RDB$RELATION_CONSTRAINTS.RDB$CONSTRAINT_TYPE, RDB$RELATION_CONSTRAINTS.RDB$RELATION_NAME FROM RDB$RELATION_CONSTRAINTS WHERE ((RDB$RELATION_CONSTRAINTS.RDB$CONSTRAINT_TYPE = 'CHECK'));
Ограничения, типа CHECK, построены на основе триггеров. Следующий запрос позволяет получить наименования данных триггеров. Имя триггера находится в поле RDB$CHECK_CONSTRAINTS.RDB$TRIGGER_NAME.
SELECT RDB$RELATION_CONSTRAINTS.RDB$CONSTRAINT_NAME, RDB$RELATION_CONSTRAINTS.RDB$CONSTRAINT_TYPE, RDB$RELATION_CONSTRAINTS.RDB$RELATION_NAME, RDB$CHECK_CONSTRAINTS.RDB$TRIGGER_NAME FROM RDB$RELATION_CONSTRAINTS INNER JOIN RDB$CHECK_CONSTRAINTS ON (RDB$RELATION_CONSTRAINTS.RDB$CONSTRAINT_NAME = RDB$CHECK_CONSTRAINTS.RDB$CONSTRAINT_NAME) WHERE ((RDB$RELATION_CONSTRAINTS.RDB$CONSTRAINT_TYPE = 'CHECK'));
Запрос, аналогичный предыдущему, позволяет выбрать NOT NULL ограничения, а также имена полей таблиц (RDB$CHECK_CONSTRAINTS.RDB$TRIGGER_NAME), на которые они распространены.
SELECT RDB$RELATION_CONSTRAINTS.RDB$CONSTRAINT_NAME, RDB$RELATION_CONSTRAINTS.RDB$CONSTRAINT_TYPE, RDB$RELATION_CONSTRAINTS.RDB$RELATION_NAME, RDB$CHECK_CONSTRAINTS.RDB$TRIGGER_NAME FROM RDB$RELATION_CONSTRAINTS INNER JOIN RDB$CHECK_CONSTRAINTS ON (RDB$RELATION_CONSTRAINTS.RDB$CONSTRAINT_NAME = RDB$CHECK_CONSTRAINTS.RDB$CONSTRAINT_NAME) WHERE ((RDB$RELATION_CONSTRAINTS.RDB$CONSTRAINT_TYPE = 'NOT NULL')) ORDER BY RDB$RELATION_CONSTRAINTS.RDB$RELATION_NAME;
Ограничения, типа PRIMARY KEY, FOREIGN KEY и UNIQUE, базируются на индексах. Следующий пример запроса позволяет выбрать такие ограничения вместе с наименованиями индексов, на которых они базируются.
SELECT RDB$RELATION_CONSTRAINTS.RDB$CONSTRAINT_NAME, RDB$RELATION_CONSTRAINTS.RDB$CONSTRAINT_TYPE, RDB$RELATION_CONSTRAINTS.RDB$RELATION_NAME, RDB$RELATION_CONSTRAINTS.RDB$INDEX_NAME FROM RDB$RELATION_CONSTRAINTS WHERE (RDB$RELATION_CONSTRAINTS.RDB$CONSTRAINT_TYPE = 'PRIMARY KEY') or (RDB$RELATION_CONSTRAINTS.RDB$CONSTRAINT_TYPE = 'FOREIGN KEY') or (RDB$RELATION_CONSTRAINTS.RDB$CONSTRAINT_TYPE = 'UNIQUE') ORDER BY RDB$RELATION_CONSTRAINTS.RDB$RELATION_NAME;
Таблица RDB$INDICES содержит связи между индексами PRIMARY KEY и FOREIGN KEY. Используя, эту информацию можно написать запрос, который покажет связи между таблицами, а также индексы, участвующие в формировании этих связей.
SELECT RDB$INDICES.RDB$RELATION_NAME, RDB$INDICES.RDB$INDEX_NAME, RDB$INDICES1.RDB$RELATION_NAME, RDB$INDICES1.RDB$INDEX_NAME FROM RDB$INDICES INNER JOIN RDB$INDICES RDB$INDICES1 ON (RDB$INDICES.RDB$FOREIGN_KEY = RDB$INDICES1.RDB$INDEX_NAME) ORDER BY RDB$INDICES.RDB$RELATION_NAME;
Изменив предыдущий запрос, а именно добавив в него таблицу RDB$RELATION_CONSTRAINTS, можно получить пары master-detail с информацией об индексах, участвующих в организации связи, и наименованиях ограничений.
SELECT RDB$INDICES.RDB$RELATION_NAME, RDB$INDICES.RDB$INDEX_NAME, RDB$RELATION_CONSTRAINTS.RDB$CONSTRAINT_NAME, RDB$RELATION_CONSTRAINTS.RDB$CONSTRAINT_TYPE, RDB$INDICES1.RDB$RELATION_NAME, RDB$INDICES1.RDB$INDEX_NAME, RDB$RELATION_CONSTRAINTS1.RDB$CONSTRAINT_NAME, RDB$RELATION_CONSTRAINTS1.RDB$CONSTRAINT_TYPE FROM RDB$INDICES INNER JOIN RDB$INDICES RDB$INDICES1 ON (RDB$INDICES.RDB$FOREIGN_KEY = RDB$INDICES1.RDB$INDEX_NAME) INNER JOIN RDB$RELATION_CONSTRAINTS RDB$RELATION_CONSTRAINTS1 ON (RDB$INDICES1.RDB$INDEX_NAME = RDB$RELATION_CONSTRAINTS1.RDB$INDEX_NAME) INNER JOIN RDB$RELATION_CONSTRAINTS ON (RDB$INDICES.RDB$INDEX_NAME = RDB$RELATION_CONSTRAINTS.RDB$INDEX_NAME) ORDER BY RDB$INDICES.RDB$RELATION_NAME;
Таблица RDB$ REF_CONSTRAINTS содержит список всех вторичных ключей с поставленными в соответствие первичными ключами. Использую этот факт можно написать еще один вариант запроса, который выдаст пары master-detail.
SELECT RDB$RELATION_CONSTRAINTS.RDB$CONSTRAINT_NAME, RDB$RELATION_CONSTRAINTS.RDB$CONSTRAINT_TYPE, RDB$RELATION_CONSTRAINTS.RDB$RELATION_NAME, RDB$RELATION_CONSTRAINTS1.RDB$CONSTRAINT_NAME, RDB$RELATION_CONSTRAINTS1.RDB$CONSTRAINT_TYPE, RDB$RELATION_CONSTRAINTS1.RDB$RELATION_NAME FROM RDB$REF_CONSTRAINTS INNER JOIN RDB$RELATION_CONSTRAINTS ON (RDB$REF_CONSTRAINTS.RDB$CONSTRAINT_NAME = RDB$RELATION_CONSTRAINTS.RDB$CONSTRAINT_NAME) INNER JOIN RDB$RELATION_CONSTRAINTS RDB$RELATION_CONSTRAINTS1 ON (RDB$REF_CONSTRAINTS.RDB$CONST_NAME_UQ = RDB$RELATION_CONSTRAINTS1.RDB$CONSTRAINT_NAME) ORDER BY RDB$RELATION_CONSTRAINTS.RDB$RELATION_NAME;
А вот так можно получить пары master-detail с указанием правил поведения при обновлении и удалении в master таблице.
SELECT RDB$RELATION_CONSTRAINTS.RDB$CONSTRAINT_NAME, RDB$RELATION_CONSTRAINTS.RDB$CONSTRAINT_TYPE, RDB$RELATION_CONSTRAINTS.RDB$RELATION_NAME, RDB$RELATION_CONSTRAINTS1.RDB$CONSTRAINT_NAME, RDB$RELATION_CONSTRAINTS1.RDB$CONSTRAINT_TYPE, RDB$RELATION_CONSTRAINTS1.RDB$RELATION_NAME, RDB$REF_CONSTRAINTS.RDB$UPDATE_RULE, RDB$REF_CONSTRAINTS.RDB$DELETE_RULE FROM RDB$REF_CONSTRAINTS INNER JOIN RDB$RELATION_CONSTRAINTS ON (RDB$REF_CONSTRAINTS.RDB$CONSTRAINT_NAME = RDB$RELATION_CONSTRAINTS.RDB$CONSTRAINT_NAME) INNER JOIN RDB$RELATION_CONSTRAINTS RDB$RELATION_CONSTRAINTS1 ON (RDB$REF_CONSTRAINTS.RDB$CONST_NAME_UQ = RDB$RELATION_CONSTRAINTS1.RDB$CONSTRAINT_NAME) ORDER BY RDB$RELATION_CONSTRAINTS.RDB$RELATION_NAME;
Пользователи
Получать информацию из базы данных могут далеко не все, а только зарегистрированные пользователи, обладающие определенными правами на объекты базы данных. В последних версиях InterBase список пользователей храниться в базе данных admin.ib (в более ранних версиях в Isc4.gdb).
Вот так можно получить перечень пользователей базы данных.
SELECT USER_NAME, FIRST_NAME, MIDDLE_NAME, LAST_NAME FROM USERS ORDER BY USER_NAME;
Из таблицы rdb$roles можно получить перечень ролей.
SELECT * from rdb$roles;
Привилегии могут выдаваться пользователям, ролям и другим объектам базы данных. Приведенный ниже запрос возвращает информацию о том, кому (RDB$USER_PRIVILEGES.RDB$USER), кто (RDB$USER_PRIVILEGES.RDB$GRANTOR), какую привилегию (RDB$USER_PRIVILEGES.RDB$PRIVILEGE) и на какой объект (RDB$USER_PRIVILEGES.RDB$RELATION_NAME) выдал. Также возвращается информация о том, может ли получивший привилегию объект передать ее другому (RDB$USER_PRIVILEGES.RDB$GRANT_OPTION), а также указывается, на весь объект была дана привилегия или на его часть (RDB$USER_PRIVILEGES.RDB$FIELD_NAME).
SELECT RDB$USER_PRIVILEGES.RDB$USER, RDB$USER_PRIVILEGES.RDB$GRANTOR, RDB$USER_PRIVILEGES.RDB$PRIVILEGE, RDB$USER_PRIVILEGES.RDB$GRANT_OPTION, RDB$USER_PRIVILEGES.RDB$RELATION_NAME, RDB$USER_PRIVILEGES.RDB$FIELD_NAME, RDB$TYPES.RDB$TYPE_NAME, RDB$TYPES1.RDB$TYPE_NAME FROM RDB$USER_PRIVILEGES INNER JOIN RDB$TYPES ON (RDB$USER_PRIVILEGES.RDB$USER_TYPE = RDB$TYPES.RDB$TYPE) INNER JOIN RDB$TYPES RDB$TYPES1 ON (RDB$USER_PRIVILEGES.RDB$OBJECT_TYPE = RDB$TYPES1.RDB$TYPE) WHERE ((RDB$TYPES.RDB$FIELD_NAME = 'RDB$OBJECT_TYPE') and (RDB$TYPES1.RDB$FIELD_NAME = 'RDB$OBJECT_TYPE')) ORDER BY RDB$USER_PRIVILEGES.RDB$USER;
Аналогичный запрос, но возвращает информацию только для ролей (какие привилегии выданы ролям).
SELECT RDB$USER_PRIVILEGES.RDB$USER, RDB$USER_PRIVILEGES.RDB$GRANTOR, RDB$USER_PRIVILEGES.RDB$PRIVILEGE, RDB$USER_PRIVILEGES.RDB$GRANT_OPTION, RDB$USER_PRIVILEGES.RDB$RELATION_NAME, RDB$USER_PRIVILEGES.RDB$FIELD_NAME, RDB$TYPES.RDB$TYPE_NAME, RDB$TYPES1.RDB$TYPE_NAME FROM RDB$USER_PRIVILEGES INNER JOIN RDB$TYPES ON (RDB$USER_PRIVILEGES.RDB$USER_TYPE = RDB$TYPES.RDB$TYPE) INNER JOIN RDB$TYPES RDB$TYPES1 ON (RDB$USER_PRIVILEGES.RDB$OBJECT_TYPE = RDB$TYPES1.RDB$TYPE) INNER JOIN RDB$ROLES ON (RDB$USER_PRIVILEGES.RDB$USER = RDB$ROLES.RDB$ROLE_NAME) WHERE ( (RDB$TYPES.RDB$FIELD_NAME = 'RDB$OBJECT_TYPE') and (RDB$TYPES1.RDB$FIELD_NAME = 'RDB$OBJECT_TYPE') and (RDB$ROLES.RDB$ROLE_NAME = RDB$USER_PRIVILEGES.RDB$USER) ) ORDER BY RDB$USER_PRIVILEGES.RDB$USER;
Вот так можно посмотреть, какие привилегии даны триггерам.
SELECT RDB$USER_PRIVILEGES.RDB$USER, RDB$USER_PRIVILEGES.RDB$GRANTOR, RDB$USER_PRIVILEGES.RDB$PRIVILEGE, RDB$USER_PRIVILEGES.RDB$GRANT_OPTION, RDB$USER_PRIVILEGES.RDB$RELATION_NAME, RDB$USER_PRIVILEGES.RDB$FIELD_NAME, RDB$TYPES.RDB$TYPE_NAME, RDB$TYPES1.RDB$TYPE_NAME FROM RDB$USER_PRIVILEGES INNER JOIN RDB$TYPES ON (RDB$USER_PRIVILEGES.RDB$USER_TYPE = RDB$TYPES.RDB$TYPE) INNER JOIN RDB$TYPES RDB$TYPES1 ON (RDB$USER_PRIVILEGES.RDB$OBJECT_TYPE = RDB$TYPES1.RDB$TYPE) WHERE ((RDB$TYPES.RDB$FIELD_NAME = 'RDB$OBJECT_TYPE') and (RDB$TYPES1.RDB$FIELD_NAME = 'RDB$OBJECT_TYPE') and (RDB$TYPES.RDB$TYPE_NAME = 'TRIGGER')) ORDER BY RDB$USER_PRIVILEGES.RDB$USER;
Следующий запрос показывает, привилегии каких ролей каким пользователям выданы.
SELECT RDB$USER_PRIVILEGES.RDB$USER, RDB$USER_PRIVILEGES.RDB$GRANTOR, RDB$USER_PRIVILEGES.RDB$PRIVILEGE, RDB$USER_PRIVILEGES.RDB$GRANT_OPTION, RDB$USER_PRIVILEGES.RDB$RELATION_NAME, RDB$USER_PRIVILEGES.RDB$FIELD_NAME, RDB$TYPES.RDB$TYPE_NAME, RDB$TYPES1.RDB$TYPE_NAME FROM RDB$USER_PRIVILEGES INNER JOIN RDB$TYPES ON (RDB$USER_PRIVILEGES.RDB$USER_TYPE = RDB$TYPES.RDB$TYPE) INNER JOIN RDB$TYPES RDB$TYPES1 ON (RDB$USER_PRIVILEGES.RDB$OBJECT_TYPE = RDB$TYPES1.RDB$TYPE) INNER JOIN RDB$ROLES ON (RDB$USER_PRIVILEGES.RDB$RELATION_NAME = RDB$ROLES.RDB$ROLE_NAME) WHERE ((RDB$TYPES.RDB$FIELD_NAME = 'RDB$OBJECT_TYPE') and (RDB$TYPES1.RDB$FIELD_NAME = 'RDB$OBJECT_TYPE')) ORDER BY RDB$USER_PRIVILEGES.RDB$USER;
Процедуры
Сохраненные процедуры - это готовые части кода, которые хранятся и выполняются в базе данных на сервере. Приведу несколько примеров, позволяющих получить информацию о них.
Следующий пример выбирает все процедуры, созданные пользователем.
SELECT RDB$PROCEDURES.RDB$PROCEDURE_NAME, RDB$PROCEDURES.RDB$SYSTEM_FLAG FROM RDB$PROCEDURES WHERE ( (RDB$PROCEDURES.RDB$SYSTEM_FLAG = 0) );
Данный запрос позволяет узнать остальную наиболее интересную информацию о процедурах, кроме наименования: количество входных и выходных переменных, описание и исходный текст процедуры и хозяина, создавшего процедуру
SELECT RDB$PROCEDURES.RDB$PROCEDURE_NAME, RDB$PROCEDURES.RDB$PROCEDURE_INPUTS, RDB$PROCEDURES.RDB$PROCEDURE_OUTPUTS, RDB$PROCEDURES.RDB$DESCRIPTION, RDB$PROCEDURES.RDB$PROCEDURE_SOURCE, RDB$PROCEDURES.RDB$OWNER_NAME, RDB$PROCEDURES.RDB$SYSTEM_FLAG FROM RDB$PROCEDURES WHERE ( (RDB$PROCEDURES.RDB$SYSTEM_FLAG = 0) );
Следующий запрос, использую информацию из таблицы RDB$PROCEDURE_PARAMETERS, выдает сведения о параметрах процедуры.
SELECT RDB$PROCEDURES.RDB$PROCEDURE_NAME, RDB$PROCEDURES.RDB$PROCEDURE_INPUTS, RDB$PROCEDURES.RDB$PROCEDURE_OUTPUTS, RDB$PROCEDURE_PARAMETERS.RDB$PARAMETER_NAME, RDB$PROCEDURE_PARAMETERS.RDB$PARAMETER_NUMBER, RDB$PROCEDURE_PARAMETERS.RDB$PARAMETER_TYPE, RDB$PROCEDURE_PARAMETERS.RDB$DESCRIPTION FROM RDB$PROCEDURES LEFT OUTER JOIN RDB$PROCEDURE_PARAMETERS ON (RDB$PROCEDURES.RDB$PROCEDURE_NAME = RDB$PROCEDURE_PARAMETERS.RDB$PROCEDURE_NAME) WHERE ((RDB$PROCEDURES.RDB$SYSTEM_FLAG = 0) or (RDB$PROCEDURES.RDB$SYSTEM_FLAG IS NULL)) ORDER BY RDB$PROCEDURES.RDB$PROCEDURE_NAME, RDB$PROCEDURE_PARAMETERS.RDB$PARAMETER_TYPE, RDB$PROCEDURE_PARAMETERS.RDB$PARAMETER_NUMBER;
А, используя информацию из таблиц RDB$FIELDS и RDB$TYPES, можно узнать типы параметров процедур и др. их характеристики.
SELECT RDB$PROCEDURES.RDB$PROCEDURE_NAME, RDB$PROCEDURES.RDB$PROCEDURE_INPUTS, RDB$PROCEDURES.RDB$PROCEDURE_OUTPUTS, RDB$PROCEDURE_PARAMETERS.RDB$PARAMETER_NAME, RDB$PROCEDURE_PARAMETERS.RDB$PARAMETER_NUMBER, RDB$PROCEDURE_PARAMETERS.RDB$PARAMETER_TYPE, RDB$PROCEDURE_PARAMETERS.RDB$DESCRIPTION, RDB$FIELDS.RDB$FIELD_NAME, RDB$FIELDS.RDB$FIELD_LENGTH, RDB$FIELDS.RDB$FIELD_SCALE, RDB$FIELDS.RDB$FIELD_TYPE, RDB$FIELDS.RDB$NULL_FLAG, RDB$TYPES.RDB$TYPE_NAME FROM RDB$PROCEDURES LEFT OUTER JOIN RDB$PROCEDURE_PARAMETERS ON (RDB$PROCEDURES.RDB$PROCEDURE_NAME = RDB$PROCEDURE_PARAMETERS.RDB$PROCEDURE_NAME) INNER JOIN RDB$FIELDS ON (RDB$PROCEDURE_PARAMETERS.RDB$FIELD_SOURCE = RDB$FIELDS.RDB$FIELD_NAME) INNER JOIN RDB$TYPES ON (RDB$FIELDS.RDB$FIELD_TYPE = RDB$TYPES.RDB$TYPE) WHERE ( ((RDB$PROCEDURES.RDB$SYSTEM_FLAG = 0) or (RDB$PROCEDURES.RDB$SYSTEM_FLAG IS NULL)) and (RDB$TYPES.RDB$FIELD_NAME = 'RDB$FIELD_TYPE') ) ORDER BY RDB$PROCEDURES.RDB$PROCEDURE_NAME, RDB$PROCEDURE_PARAMETERS.RDB$PARAMETER_TYPE, RDB$PROCEDURE_PARAMETERS.RDB$PARAMETER_NUMBER;
Псевдонимы типов (Домены)
В псевдониме типа сосредоточено много информации о том поле таблицы, которое базируется на нем. Часть из них определяет поля служебных таблиц, остальные описывают поля пользовательских таблиц. Домены могут быть созданы пользователями и, в дальнейшем, использоваться для описания полей, создаваемых таблиц. Если при создании таблиц не используются пользовательские домены, то сервер сам автоматически создаст домены для каждого поля таблицы. Имена таких доменов, а также доменов для служебных таблиц, начинаются с RDB$. Не стоим указывать такой префикс при создании пользовательских доменов.
Практически вся информация о доменах хранится в таблице RDB$FIELDS. Попробуем получить максимум информации из этой таблицы. Приведенный ниже запрос возвращает данные обо всех доменах базы данных.
SELECT RDB$FIELDS.RDB$FIELD_NAME, RDB$FIELDS.RDB$QUERY_NAME, RDB$FIELDS.RDB$VALIDATION_BLR, RDB$FIELDS.RDB$VALIDATION_SOURCE, RDB$FIELDS.RDB$COMPUTED_BLR, RDB$FIELDS.RDB$COMPUTED_SOURCE, RDB$FIELDS.RDB$DEFAULT_VALUE, RDB$FIELDS.RDB$DEFAULT_SOURCE, RDB$FIELDS.RDB$FIELD_LENGTH, RDB$FIELDS.RDB$FIELD_SCALE, RDB$FIELDS.RDB$FIELD_TYPE, RDB$FIELDS.RDB$FIELD_SUB_TYPE, RDB$FIELDS.RDB$MISSING_VALUE, RDB$FIELDS.RDB$MISSING_SOURCE, RDB$FIELDS.RDB$DESCRIPTION, RDB$FIELDS.RDB$SYSTEM_FLAG, RDB$FIELDS.RDB$QUERY_HEADER, RDB$FIELDS.RDB$SEGMENT_LENGTH, RDB$FIELDS.RDB$EDIT_STRING, RDB$FIELDS.RDB$EXTERNAL_LENGTH, RDB$FIELDS.RDB$EXTERNAL_SCALE, RDB$FIELDS.RDB$EXTERNAL_TYPE, RDB$FIELDS.RDB$DIMENSIONS, RDB$FIELDS.RDB$NULL_FLAG, RDB$FIELDS.RDB$CHARACTER_LENGTH, RDB$FIELDS.RDB$COLLATION_ID, RDB$FIELDS.RDB$CHARACTER_SET_ID, RDB$FIELDS.RDB$FIELD_PRECISION FROM RDB$FIELDS;
Наиболее интересными полями в этом запросе являются: RDB$FIELD_NAME - наименование домена; RDB$VALIDATION_SOURCE - текст проверок вводимых данных; RDB$COMPUTED_SOURCE - текст формулы для вычислимых полей; RDB$DEFAULT_SOURCE - значение по умолчанию; RDB$FIELD_LENGTH, RDB$FIELD_SCALE, RDB$FIELD_TYPE - величина и тип домена; RDB$SYSTEM_FLAG - признак того, что домен пользовательский (0) или созданный сервером (1); RDB$NULL_FLAG - возможно или нет (1) наличие значения NULL.
Предыдущий запрос выдавал информацию обо всех доменах базы данных. Чаще всего бывает интересно проанализировать пользовательские домены. Следующий пример запроса выдает наиболее интересную информацию только о доменах, созданных пользователем.
SELECT RDB$FIELDS.RDB$FIELD_NAME, RDB$FIELDS.RDB$COMPUTED_SOURCE, RDB$FIELDS.RDB$DEFAULT_SOURCE, RDB$FIELDS.RDB$FIELD_LENGTH, RDB$FIELDS.RDB$FIELD_SCALE, RDB$FIELDS.RDB$FIELD_TYPE, RDB$FIELDS.RDB$FIELD_SUB_TYPE, RDB$FIELDS.RDB$DESCRIPTION, RDB$FIELDS.RDB$SYSTEM_FLAG, RDB$FIELDS.RDB$DIMENSIONS, RDB$FIELDS.RDB$NULL_FLAG, RDB$FIELDS.RDB$CHARACTER_LENGTH, RDB$FIELDS.RDB$COLLATION_ID, RDB$FIELDS.RDB$CHARACTER_SET_ID, RDB$FIELDS.RDB$FIELD_PRECISION FROM RDB$FIELDS WHERE (RDB$FIELDS.RDB$SYSTEM_FLAG = 0);
Некоторые параметры (на пример тип) представлены в виде кодов. У этих кодов есть расшифровка, которая содержится в таблице RDB$TYPES. Вот пример, который позволяет посмотреть текстовое описание типа домена.
SELECT RDB$FIELDS.RDB$FIELD_NAME, RDB$FIELDS.RDB$COMPUTED_SOURCE, RDB$FIELDS.RDB$DEFAULT_SOURCE, RDB$FIELDS.RDB$FIELD_LENGTH, RDB$FIELDS.RDB$FIELD_SCALE, RDB$FIELDS.RDB$FIELD_TYPE, RDB$TYPES.RDB$TYPE_NAME, RDB$FIELDS.RDB$FIELD_SUB_TYPE, RDB$FIELDS.RDB$DESCRIPTION, RDB$FIELDS.RDB$SYSTEM_FLAG, RDB$FIELDS.RDB$DIMENSIONS, RDB$FIELDS.RDB$NULL_FLAG, RDB$FIELDS.RDB$CHARACTER_LENGTH, RDB$FIELDS.RDB$COLLATION_ID, RDB$FIELDS.RDB$CHARACTER_SET_ID, RDB$FIELDS.RDB$FIELD_PRECISION FROM RDB$FIELDS LEFT OUTER JOIN RDB$TYPES ON (RDB$FIELDS.RDB$FIELD_TYPE = RDB$TYPES.RDB$TYPE) WHERE ((RDB$FIELDS.RDB$SYSTEM_FLAG = 0) and (RDB$TYPES.RDB$FIELD_NAME = 'RDB$FIELD_TYPE'));
В полях базы данных InterBase могут храниться массивы, соответственно можно определить домен, который будет содержать информацию о массиве. Следующий запрос выбирает именно такие домены и выдает информацию о размерности и границах массива. Информация о размерности массива содержится в таблице RDB$FIELD_DIMENSIONS.
SELECT RDB$FIELDS.RDB$FIELD_NAME, RDB$FIELDS.RDB$COMPUTED_SOURCE, RDB$FIELDS.RDB$DEFAULT_SOURCE, RDB$FIELDS.RDB$FIELD_LENGTH, RDB$FIELDS.RDB$FIELD_SCALE, RDB$FIELDS.RDB$FIELD_TYPE, RDB$TYPES.RDB$TYPE_NAME, RDB$FIELDS.RDB$FIELD_SUB_TYPE, RDB$FIELDS.RDB$DESCRIPTION, RDB$FIELDS.RDB$DIMENSIONS, RDB$FIELD_DIMENSIONS.RDB$DIMENSION, RDB$FIELD_DIMENSIONS.RDB$LOWER_BOUND, RDB$FIELD_DIMENSIONS.RDB$UPPER_BOUND FROM RDB$FIELDS INNER JOIN RDB$FIELD_DIMENSIONS ON (RDB$FIELDS.RDB$FIELD_NAME = RDB$FIELD_DIMENSIONS.RDB$FIELD_NAME) LEFT OUTER JOIN RDB$TYPES ON (RDB$FIELDS.RDB$FIELD_TYPE = RDB$TYPES.RDB$TYPE) WHERE ((RDB$FIELDS.RDB$SYSTEM_FLAG = 0) and (RDB$TYPES.RDB$FIELD_NAME = 'RDB$FIELD_TYPE'));
При создании текстовых полей можно указать кодировку, в которой будет храниться текст. Соответственно, можно и посмотреть, какая кодовая страница задана для конкретного поля. Информация о кодовых страницах хранится в таблице RDB$CHARACTER_SETS.
SELECT RDB$FIELDS.RDB$FIELD_NAME, RDB$FIELDS.RDB$COMPUTED_SOURCE, RDB$FIELDS.RDB$DEFAULT_SOURCE, RDB$FIELDS.RDB$FIELD_LENGTH, RDB$FIELDS.RDB$FIELD_SCALE, RDB$FIELDS.RDB$FIELD_TYPE, RDB$TYPES.RDB$TYPE_NAME, RDB$FIELDS.RDB$FIELD_SUB_TYPE, RDB$FIELDS.RDB$DESCRIPTION, RDB$FIELDS.RDB$DIMENSIONS, RDB$FIELDS.RDB$CHARACTER_SET_ID, RDB$CHARACTER_SETS.RDB$CHARACTER_SET_NAME FROM RDB$FIELDS LEFT OUTER JOIN RDB$TYPES ON (RDB$FIELDS.RDB$FIELD_TYPE = RDB$TYPES.RDB$TYPE) LEFT OUTER JOIN RDB$CHARACTER_SETS ON (RDB$FIELDS.RDB$CHARACTER_SET_ID = RDB$CHARACTER_SETS.RDB$CHARACTER_SET_ID) WHERE ((RDB$FIELDS.RDB$SYSTEM_FLAG = 0) and (RDB$TYPES.RDB$FIELD_NAME = 'RDB$FIELD_TYPE'));
Таблицы
Таблицы и представления близки между собой по "внешнему виду", не смотря на то, что по сути своей это разные объекты. Но мы часто обращаемся к ним в базе данных, используя аналогичные конструкции языка, и не задумываемся об их внутренней организации. Видимо это обстоятельство и послужило основой тому, что информация о них хранится в служебных таблицах вместе.
Приведенный запрос позволяет получить перечень таблиц и представлений, исходный код представлений, их описание, данное пользователем, имя, количество полей и хозяина таблицы или представления.
SELECT RDB$RELATIONS.RDB$VIEW_SOURCE, RDB$RELATIONS.RDB$DESCRIPTION, RDB$RELATIONS.RDB$RELATION_NAME, RDB$RELATIONS.RDB$FIELD_ID, RDB$RELATIONS.RDB$OWNER_NAME FROM RDB$RELATIONS WHERE ( (RDB$RELATIONS.RDB$SYSTEM_FLAG = 0) ) ORDER BY RDB$RELATIONS.RDB$RELATION_NAME;
Выражение RDB$RELATIONS.RDB$SYSTEM_FLAG = 0 позволят отобрать только объекты, созданные пользователем. Наложив на запрос еще одно условие (RDB$RELATIONS.RDB$VIEW_SOURCE IS NULL), можно получить перечень только таблиц.
SELECT RDB$RELATIONS.RDB$DESCRIPTION, RDB$RELATIONS.RDB$RELATION_NAME, RDB$RELATIONS.RDB$FIELD_ID, RDB$RELATIONS.RDB$OWNER_NAME FROM RDB$RELATIONS WHERE ((RDB$RELATIONS.RDB$SYSTEM_FLAG = 0) and (RDB$RELATIONS.RDB$VIEW_SOURCE IS NULL )) ORDER BY RDB$RELATIONS.RDB$RELATION_NAME;
Замена описанного выше условия на обратное (RDB$RELATIONS.RDB$VIEW_SOURCE IS NOT NULL) позволит получить только перечень представлений.
SELECT RDB$RELATIONS.RDB$DESCRIPTION, RDB$RELATIONS.RDB$VIEW_SOURCE, RDB$RELATIONS.RDB$RELATION_NAME, RDB$RELATIONS.RDB$FIELD_ID, RDB$RELATIONS.RDB$OWNER_NAME FROM RDB$RELATIONS WHERE ((RDB$RELATIONS.RDB$SYSTEM_FLAG = 0) and (RDB$RELATIONS.RDB$VIEW_SOURCE IS NOT NULL )) ORDER BY RDB$RELATIONS.RDB$RELATION_NAME;
Каждое изменение методанных таблицы фиксируется сервером в таблице RDB$FORMATS и таких изменений может быть только 256. По достижении данной границы необходимо выполнить backup/restore для сброса этих счетчиков.
Приведенный ниже запрос позволяет нам узнать, сколько уже было сделано изменений для каждой таблицы.
SELECT RDB$RELATIONS.RDB$RELATION_NAME, MAX( RDB$FORMATS.RDB$FORMAT ) MAX_OF_RDB$FORMAT FROM RDB$RELATIONS INNER JOIN RDB$FORMATS ON (RDB$RELATIONS.RDB$RELATION_ID = RDB$FORMATS.RDB$RELATION_ID) WHERE ((RDB$RELATIONS.RDB$SYSTEM_FLAG = 0) and (RDB$RELATIONS.RDB$VIEW_SOURCE IS NULL)) GROUP BY RDB$RELATIONS.RDB$RELATION_NAME ORDER BY RDB$RELATIONS.RDB$RELATION_NAME;
Все таблицы состоят из полей. Посмотрим, из каких полей состоит та или иная таблица. Информация о полях таблиц содержится в служебной таблице RDB$RELATION_FIELDS. Приведенный ниже запрос выдаст имя таблицы, имя поля, порядковый номер поля в таблице, имя домена (RDB$FIELD_SOURCE), на котором данное поле базируется, возможность ввести NULL (RDB$NULL_FLAG <> 1) и значение по умолчанию.
SELECT RDB$RELATIONS.RDB$RELATION_NAME, RDB$RELATION_FIELDS.RDB$FIELD_NAME, RDB$RELATION_FIELDS.RDB$FIELD_POSITION, RDB$RELATION_FIELDS.RDB$FIELD_SOURCE, RDB$RELATION_FIELDS.RDB$NULL_FLAG, RDB$RELATION_FIELDS.RDB$DEFAULT_SOURCE FROM RDB$RELATION_FIELDS INNER JOIN RDB$RELATIONS ON (RDB$RELATION_FIELDS.RDB$RELATION_NAME = RDB$RELATIONS.RDB$RELATION_NAME) WHERE ((RDB$RELATIONS.RDB$SYSTEM_FLAG = 0) and (RDB$RELATIONS.RDB$VIEW_SOURCE IS NULL)) ORDER BY RDB$RELATIONS.RDB$RELATION_NAME, RDB$RELATION_FIELDS.RDB$FIELD_POSITION;
Если включить в запрос информацию из таблицы RDB$FIELDS (описание доменов), то можно получить дополнительную информацию о каждом поле. Такой информацией может быть выражение для вычислимых полей и тип поля базы данных.
SELECT RDB$RELATIONS.RDB$RELATION_NAME, RDB$RELATION_FIELDS.RDB$FIELD_NAME, RDB$RELATION_FIELDS.RDB$FIELD_POSITION, RDB$RELATION_FIELDS.RDB$FIELD_SOURCE, RDB$RELATION_FIELDS.RDB$NULL_FLAG, RDB$RELATION_FIELDS.RDB$DEFAULT_SOURCE, RDB$FIELDS.RDB$COMPUTED_SOURCE, RDB$FIELDS.RDB$DEFAULT_SOURCE, RDB$FIELDS.RDB$FIELD_TYPE, RDB$FIELDS.RDB$NULL_FLAG FROM RDB$RELATIONS INNER JOIN RDB$RELATION_FIELDS ON (RDB$RELATIONS.RDB$RELATION_NAME = RDB$RELATION_FIELDS.RDB$RELATION_NAME) INNER JOIN RDB$FIELDS ON (RDB$RELATION_FIELDS.RDB$FIELD_SOURCE = RDB$FIELDS.RDB$FIELD_NAME) WHERE ((RDB$RELATIONS.RDB$SYSTEM_FLAG = 0) and (RDB$RELATIONS.RDB$VIEW_SOURCE IS NULL)) ORDER BY RDB$RELATIONS.RDB$RELATION_NAME, RDB$RELATION_FIELDS.RDB$FIELD_POSITION;
Приведенный ниже запрос написан с включением в него таблицы RDB$TYPES, из которой можно получить текстовое описание типа поля.
SELECT RDB$RELATIONS.RDB$RELATION_NAME, RDB$RELATION_FIELDS.RDB$FIELD_NAME, RDB$RELATION_FIELDS.RDB$FIELD_POSITION, RDB$RELATION_FIELDS.RDB$FIELD_SOURCE, RDB$RELATION_FIELDS.RDB$NULL_FLAG, RDB$RELATION_FIELDS.RDB$DEFAULT_SOURCE, RDB$FIELDS.RDB$COMPUTED_SOURCE, RDB$FIELDS.RDB$DEFAULT_SOURCE, RDB$FIELDS.RDB$NULL_FLAG, RDB$FIELDS.RDB$FIELD_TYPE, RDB$TYPES.RDB$TYPE_NAME, RDB$FIELDS.RDB$FIELD_SUB_TYPE, RDB$FIELDS.RDB$FIELD_LENGTH, RDB$FIELDS.RDB$FIELD_SCALE FROM RDB$RELATIONS INNER JOIN RDB$RELATION_FIELDS ON (RDB$RELATIONS.RDB$RELATION_NAME = RDB$RELATION_FIELDS.RDB$RELATION_NAME) INNER JOIN RDB$FIELDS ON (RDB$RELATION_FIELDS.RDB$FIELD_SOURCE = RDB$FIELDS.RDB$FIELD_NAME) INNER JOIN RDB$TYPES ON (RDB$FIELDS.RDB$FIELD_TYPE = RDB$TYPES.RDB$TYPE) WHERE ((RDB$RELATIONS.RDB$SYSTEM_FLAG = 0) and (RDB$RELATIONS.RDB$VIEW_SOURCE IS NULL ) and (RDB$TYPES.RDB$FIELD_NAME = 'RDB$FIELD_TYPE')) ORDER BY RDB$RELATIONS.RDB$RELATION_NAME, RDB$RELATION_FIELDS.RDB$FIELD_POSITION;
Следующий простой запрос покажет количество полей в таблицах.
SELECT RDB$RELATIONS.RDB$RELATION_NAME, count(RDB$RELATION_FIELDS.rdb$field_name) KOL FROM RDB$RELATIONS INNER JOIN RDB$RELATION_FIELDS ON (RDB$RELATIONS.RDB$RELATION_NAME = RDB$RELATION_FIELDS.RDB$RELATION_NAME) WHERE ((RDB$RELATIONS.RDB$SYSTEM_FLAG = 0) and (RDB$RELATIONS.RDB$VIEW_SOURCE IS NULL)) GROUP BY RDB$RELATIONS.RDB$RELATION_NAME ORDER BY RDB$RELATIONS.RDB$RELATION_NAME;
А этот запрос, аналогичный предыдущему, выдает количество полей, на которых построены запросы.
SELECT RDB$RELATIONS.RDB$RELATION_NAME, count(RDB$RELATION_FIELDS.rdb$field_name) KOL FROM RDB$RELATIONS INNER JOIN RDB$RELATION_FIELDS ON (RDB$RELATIONS.RDB$RELATION_NAME = RDB$RELATION_FIELDS.RDB$RELATION_NAME) WHERE ((RDB$RELATIONS.RDB$SYSTEM_FLAG = 0) and (RDB$RELATIONS.RDB$VIEW_SOURCE IS NOT NULL)) GROUP BY RDB$RELATIONS.RDB$RELATION_NAME ORDER BY RDB$RELATIONS.RDB$RELATION_NAME;
Приведу пример запроса для просмотра прав, выданных в базе данных, пользователям на таблицы. Для этого воспользуемся информацией из таблицы RDB$USER_PRIVILEGES.
Вот краткое пояснение к получившемуся результату: RDB$USER_PRIVILEGES.RDB$USER - кому выдана привилегия, RDB$USER_PRIVILEGES.RDB$GRANTOR - тот, кто выдал привилегию, RDB$USER_PRIVILEGES.RDB$PRIVILEGE - какая привилегия была выдана и было ли выражение WITH GRANT OPTION (RDB$USER_PRIVILEGES.RDB$GRANT_OPTION = 1).
SELECT RDB$RELATIONS.RDB$RELATION_NAME, RDB$USER_PRIVILEGES.RDB$USER, RDB$USER_PRIVILEGES.RDB$GRANTOR, RDB$USER_PRIVILEGES.RDB$PRIVILEGE, RDB$USER_PRIVILEGES.RDB$GRANT_OPTION FROM RDB$RELATIONS INNER JOIN RDB$USER_PRIVILEGES ON (RDB$RELATIONS.RDB$RELATION_NAME = RDB$USER_PRIVILEGES.RDB$RELATION_NAME) WHERE ((RDB$RELATIONS.RDB$SYSTEM_FLAG = 0) and (RDB$RELATIONS.RDB$VIEW_SOURCE IS NULL)) ORDER BY RDB$RELATIONS.RDB$RELATION_NAME, RDB$USER_PRIVILEGES.RDB$PRIVILEGE;
При RDB$RELATIONS.RDB$VIEW_SOURCE IS NOT NULL можно узнать привилегии для представлений.
Триггеры
Триггеры являются откомпилированным кодом, хранимым в базе данных, который исполняется сервером при возникновении определенных событий (вставка, удаление и пр.) для таблиц.
Приведенный ниже запрос позволяет выбрать триггеры, определенные пользователем и не являющиеся базой для CHECK ограничений.
SELECT RDB$TRIGGERS.RDB$TRIGGER_NAME, RDB$TRIGGERS.RDB$SYSTEM_FLAG FROM RDB$TRIGGERS WHERE ( (RDB$TRIGGERS.RDB$SYSTEM_FLAG <> 1) and (RDB$TRIGGERS.RDB$TRIGGER_NAME not in (SELECT RDB$TRIGGER_NAME FROM RDB$CHECK_CONSTRAINTS)) );
Следующий запрос аналогичен предыдущему, но он выдает более полную информацию о триггере: имя триггера, таблица, для которой этот триггер определен, порядковый номер триггера при выполнении, тип триггера, его исходный текст и описание, данное пользователем, а также признак активности.
SELECT RDB$TRIGGERS.RDB$TRIGGER_NAME, RDB$TRIGGERS.RDB$RELATION_NAME, RDB$TRIGGERS.RDB$TRIGGER_SEQUENCE, RDB$TRIGGERS.RDB$TRIGGER_TYPE, RDB$TRIGGERS.RDB$TRIGGER_SOURCE, RDB$TRIGGERS.RDB$DESCRIPTION, RDB$TRIGGERS.RDB$TRIGGER_INACTIVE FROM RDB$TRIGGERS WHERE ( (RDB$TRIGGERS.RDB$SYSTEM_FLAG <> 1) and (RDB$TRIGGERS.RDB$TRIGGER_NAME not in (SELECT RDB$TRIGGER_NAME FROM RDB$CHECK_CONSTRAINTS)) );
Используя информацию о типах из таблицы RDB$TYPES, получаем расшифровку типов триггеров.
SELECT RDB$TRIGGERS.RDB$TRIGGER_NAME, RDB$TRIGGERS.RDB$RELATION_NAME, RDB$TRIGGERS.RDB$TRIGGER_SEQUENCE, RDB$TRIGGERS.RDB$TRIGGER_TYPE, RDB$TYPES.RDB$TYPE_NAME, RDB$TRIGGERS.RDB$TRIGGER_SOURCE, RDB$TRIGGERS.RDB$DESCRIPTION, RDB$TRIGGERS.RDB$TRIGGER_INACTIVE FROM RDB$TRIGGERS INNER JOIN RDB$TYPES ON (RDB$TRIGGERS.RDB$TRIGGER_TYPE = RDB$TYPES.RDB$TYPE) WHERE ((RDB$TRIGGERS.RDB$SYSTEM_FLAG <> 1) and (RDB$TRIGGERS.RDB$TRIGGER_NAME not in (SELECT RDB$TRIGGER_NAME FROM RDB$CHECK_CONSTRAINTS)) and (RDB$TYPES.RDB$FIELD_NAME = 'RDB$TRIGGER_TYPE') ) ORDER BY RDB$TRIGGERS.RDB$RELATION_NAME, RDB$TRIGGERS.RDB$TRIGGER_SEQUENCE;
Данная небольшая статья является небольшим
Данная небольшая статья является небольшим расширением или дополнением к опубликованным ранее заметкам о системных таблицах InterBase. В ней я постарался привести все запросы в более общем виде.
Различные базы данных, базирующиеся на сервере InterBase, могут содержать различное количество таблиц, представлений, процедур, триггеров и других объектов. Но их описание, определение их взаимодействия и ограничений, наложенных на объекты, описаны в конечном наборе служебных таблиц базы данных.
Служебные таблицы создаются сервером InterBase при создании базы данных и поддерживаются им в процессе всей жизни базы. От версии к версии их количество увеличивается, предоставляя нам возможность простыми способами узнавать информацию об объектах базы и ее текущем состоянии.
Сервер InterBase сам управляет содержимым этих таблиц и использует его для управления пользовательскими объектами базы данных. Поэтому я не рекомендовал бы вносить изменения в их содержание, наоборот, нужно принять все меры для ограничения доступа к ним.
Однако, при необходимости, можно извлечь из служебных таблиц достаточно много полезной информации, имея права на чтение из них.
Зависимости
В базе данных одни объекты могут зависеть от других (на пример, хранимая процедура от таблицы, на поля которой ссылается). Поэтому сервер всегда отслеживает подобные зависимости и не допускает удаление объектов, от которых зависят другие объекты базы данных. Основная информация о зависимостях хранится в таблице RDB$DEPENDENCIES.
Приведенный ниже запрос отображает зависимость объектов (RDB$DEPENDENT_NAME) от других объектов (RDB$DEPENDED_ON_NAME), а так же возвращает перечень полей, которые образуют эту зависимость (RDB$FIELD_NAME).
SELECT RDB$DEPENDENCIES.RDB$DEPENDENT_NAME, RDB$TYPES.RDB$TYPE_NAME, RDB$DEPENDENCIES.RDB$DEPENDED_ON_NAME, RDB$TYPES1.RDB$TYPE_NAME, RDB$DEPENDENCIES.RDB$FIELD_NAME, RDB$DEPENDENCIES.RDB$DEPENDENT_TYPE, RDB$DEPENDENCIES.RDB$DEPENDED_ON_TYPE FROM RDB$DEPENDENCIES INNER JOIN RDB$TYPES ON (RDB$DEPENDENCIES.RDB$DEPENDENT_TYPE = RDB$TYPES.RDB$TYPE) INNER JOIN RDB$TYPES RDB$TYPES1 ON (RDB$DEPENDENCIES.RDB$DEPENDED_ON_TYPE = RDB$TYPES1.RDB$TYPE) WHERE ((RDB$TYPES.RDB$FIELD_NAME = 'RDB$OBJECT_TYPE') and (RDB$TYPES1.RDB$FIELD_NAME = 'RDB$OBJECT_TYPE')) ORDER BY RDB$DEPENDENCIES.RDB$DEPENDENT_NAME, RDB$DEPENDENCIES.RDB$DEPENDED_ON_NAME;
Анализ статистики базы данных InterBase/FireBird
Волох Д.А.
Компания НеРуСофт
Статистика базы данных InterBase/FireBird, полученная через gstat или Services API, предоставляет большое количество статистических данных, которые могут быть использованы для анализа работы сервера и самой базы данных. Приложения, работающие с базой данных, могут корректно работать с транзакциями и самими данными, то есть не создают длительные активные транзакции, не запускают длительные snapshot транзакции. Все это верно для небольшого количества одновременно работающих пользователей, но как только количество пользователей возрастет на порядок и следовательно увеличиться количество операций по вставке, редактированию и удалению данных, могут возникнуть проблемы по сборке, так называемого, мусора. Проведенная и проанализированная статистика может помочь в процессе сборки мусора.
С помощью утилиты gstat с соответствующими ключами или Services API можно получить следующие виды статистики:
Статистика заголовочной страницы - это информация о глобальных свойствах всей базы данных, хранящаяся на заголовочной страницы каждой базы данных ( -header ); Статистика страниц данных – информация о таблицах содержащихся в базе данных ( -data); Статистика индексов – информация об индексах в базе данных (-index); Статистика системных таблиц (-system).
Пример использования gstat:
gstat –header –user SYSDBA –password masterkey c:work.gdb
gstat –all –user SYSDBA –password masterkey c:work.gdb получаем полную статистическую информацию базы данных
Описание основных разделов статистики базы данных
Для примера будем рассматривать конкретную базу данных, которая состоит из 12 таблиц, большая часть работы производится с одной таблицей, которая ссылается на 4 справочника и ежедневный прирост записей составляет порядка 1000 записей. Общее количество записей на момент написания статьи составляет 192285 записей. Размер файла базы данных составляет 302 мегабайта.
Запустив утилиты gstat (с ключом –header)или использовав компонент FIBStatisticalServices можно получить статистические данные о нашей базе данных. Часть информации является статичной, часть – меняется в зависимости от происходящих в базе данных изменений.
Пример заголовочной информации:
Database header page information:
Следует отметить, что в нашем примере выводится вся статистическая информация, но с помощью свойства Options можно выбирать какую область данных нужно вывести: fibStatistical.Options := [DataPages]; //соответствует ключу –data ffibStatistical.Options := [HeaderPages]; //соответствует ключу –header
Процесс получения статистики проходит определенное время поэтому, чтобы не смущать пользователя простоем программы сменим курсор:
Screen.Cursor := crSQLWait;
Далее нам нужно отображать информацию в поле Мемо: while not (fibStatistical.Eof) do mDBInfo.Lines.Add(fibStatistical.GetNextLine);
По завершению выполняем следующие действия:
Screen.Cursor := crDefault; // Возвращаем курсор в стандартный вид
fibStatistical.Active := False; // Деактивируем процесс
В итоге у нас должно получиться следующее:
Резервное копирование базы данных и последующее восстановление
Волох Д.А.
Компания НеРуСофт
Резервное копирование и восстановление из копии является одним из самых важных процессов в администрировании базы данных InterBase/FireBird.
Резервное копирование – один из самых надежных способов сохранить и предохранить свои данные от потери или порчи. Процесс резервного копирования также делается в профилактических целях, для увеличения производительности базы данных – это достигается за счет того, что в момент копирования происходит считывание последних версий всех записей, старые же версии в копию никогда не попадают. Здесь важно заметить, что недостаточно одного лишь резеврного копирования, нужно иногда проверять восстанавливаемость базы данных из резеверной копии, потому что бывают случаи, что база данных работает в режиме 24*7, то есть 24 часа в сутки и 7 дней в неделю, backup базы данных может происходит нормально, но в силу определенных причин база данных не восстанавливается, последствия могут быть плачевными для всех данных.
Причины могут следующими: в базе данных есть ограничения, такие как NOT NULL поля, внешние ключи, уникальность, а существующие данные в базе данных этим ограничениям не соответствуют по каким-либо причинам. Такие данные могут мирно существовать до тех пор, пока они не будут задействованы в операциях редактирования или удаления. В процессе восстановления «прощупываются» все данные - в первую очередь создаются ограничения и затем заливаются данные, в этот момент и происходит ошибка. Для профилактики следует восстанавливать базу данных в тестовую, и лишь при успешном завершении процесса восстановления, делать Restore в текущую базу. В случае возникновения ситуации с поврежденным файлом backup’а следует найти в базе данных несоответствия и исправить их.
Рассмотрим два способа резервирования/восстановления базы данных с помощью утилиты gbak и компонентов FIBPlus:
1. Утилита gbak с соответствующими ключами позволяет совершать резервирование и восстановление базы данных, синтаксис выполнения операций следующий:
Резервирование
gbak [-B] [ключи] файл_базы_данных файл_резервной_копии
Значение ключей:
- g не собирать мусор во время резервного копирования;
-l игнорировать лимбо-транзакции;
-m резервирование только метаданных;
-user имя пользователя;
-password пароль пользователя;
-v показать протоколирование во время выполнения процесса;
-z показать версию gbak.
Пример:
gbak -b –user SYSDBA –password masterkey C:workwork.gdb C:workwork.gbk
Восстановление:
gbak [-R/-С] [ключи] файл_базы_данных файл_резервной_копии
Ключи:
-R восстановление базы данных в уже существующий файл;
-С восстановление базы данных в новый файл, если файл уже существует, то процесс будет прерван с ошибкой;
-p размер страницы (1024,2048,4196 или 8192). По умолчанию 1024;
Пример:
gbak -R –user SYSDBA –password masterkey C:workwork.gbk C:workwork.gdb
Следует отметить один нюанс, если запустить утилиту gbak без ключа –B или –R/-C, то будет начат процесс резервного копирования базы данных.
2. С помощью компонентов FIBPlus можно также программно запускать процессы резервирования и восстановления базы данных.
Для резервирования базы данных использовался компонент TpFIBBackupService, для начала процесс нужно заполнить определенные поля в компоненте: fibBackUp.DatabaseName := edBackUpPath.Text; // указываем путь к базе данных
fibBackUp.ServerName :=’localhost’; //имя сервера
// создаем и указываем имя будущего файла резервной копии базы данных
fibBackUp.BackupFile.Add(frmMain.CurrentBackUpPath + ExtractFileName(edBackUpPath.Text)
+ '_' + DateToStr(now) + '.gbk');
fibBackUp.Params.Add('user_name=SYSDBA’);
fibBackUp.Params.Add('password=masterkey');
fibBackUp.Active := True; // активируем процесс
try
Screen.Cursor := crSQLWait;
fibBackUp.ServiceStart; // и стартуем его
mBackup.Lines.Add('**************** Резервное копирование начато базы: ' + dBackUpPath.Text + '****************' );
mBackup.Lines.Add(' );
// пока не завершится процесс выводим логии о процессе, для того чтобы логии отображались нужно
свойство Verbose поставить в значение TRUE