Кузьменко Дмитрий, Epsylon Tecnologies
13 мая 1999 – оригинальная версия
25 ноября 1999 – добавлена правильная обработка GRANT и REVOKE, исправлен текст заголовка триггеров
18 января 2000 – добавлено on delete xxx/on update xxx, исправлены проблемы с GRANT и REVOKE вообще и с ROLE в частности
9 сентября 2000 – небольшие несущественные исправления
Всем, кто работает с Delphi или C++Builder, известен входящий в поставку инструмент для работы с базами данных. В Professional версиях он называется Database Explorer, в Client/Server (или Enterprise) версиях – SQL Explorer.
Обычно никто не задумывается,
как Database Explorer умудряется понимать особенности различных SQL-серверов (например, процедуры и генераторы IB Database). А делает он это очень просто. На самом деле Database Explorer – универсальная программа, которая умеет настраиваться, выполнять определенные команды и действия, отображать некую информацию в виде дерева, создавать редактировать и удалять элементы этого дерева. Особенности серверов Database Explorer учитывает при помощи специального текстового файла dbx.dbi, который находится в каталоге %Delphi%/BIN.
Мы с вами отредактируем этот файл, и научим Database Explorer понимать некоторые особенности IB Database, которые не поддерживаются в стандартной версии. Это:
- просмотр и редактирование TRIGGERS для VIEW, точно также как и для TABLE
- отображение параметра FREE_IT для UDF для IB 5.x
- отображение и редактирование параметра ACTIVE для триггеров
- просмотр и редактирование привилегий (GRANTS) для объектов Table, View, Trigger, Procedure (ветка Grants в свойствах объекта); просмотр привилегий вообще (корневая ветка Privileges)
- просмотр ролей (ROLES) для IB 5.x; просмотр участия пользователей в роли (ветка Grants в свойствах Role)
- правильное отображение опций ON DELETE/ON UPDATE для FOREIGN KEY
Почему для пунктов 4 и 5 только просмотр, спросите вы? Просто потому, что я не имею под рукой описания объектов и параметров DBX.DBI, и вся информация, приведенная ниже, получена чисто экспериментальным путем. Если вы сумеете добавить что-то свое, то
обязательно отправьте письмо
мне.
Для начала советую вам
скопировать оригинальный dbx.dbi, например, в dbx_old.dbi. Если что-то не заладится, то вы сможете вернуться к работающему варианту Database Explorer. Также, после окончания редактирования dbx.dbi сделайте его копию в dbx_new.dbi. Дело в том, что установка патчей для Delphi (а тем более установка новых версий Delphi) может записать в каталог \BIN стандартный файл dbx.dbi, вернув Database Explorer к стандартным возможностям.
Заметьте, что Database Explorer загружает этот файл при старте, поэтому если вы собрались вносить исправления в dbx.dbi, "по-новому" Database Explorer заработает только после очередного запуска.
Разумеется, если вы не хотите мучиться и копировать нижеприведенный текст в dbx.dbi, можете взять
dbx.zip. Но все-таки будет полезнее, если вы хотя бы просмотрите, как и что было изменено в этом файле:
Итак, откройте dbx.dbi в Delphi или NotePad и найдите секцию
[INTRBASE Properties]. Строку
TFnParamKind:Values=Closed,0=BY VALUE:"Value",1=:"Reference",3=:"Handle"
поменяйте на
TFnParamKind:Values=Closed,0=BY VALUE:"Value",1=:"Reference",3=:"Handle",-1=FREE_IT
и добавьте после нее строки
TPrivilegeType:Values1=Closed,R=REFERENCES ON:"REFERENCES",D=DELETE ON:"DELETE",U=UPDATE ON:"UPDATE"
TPrivilegeType:Values2=I=INSERT ON:"INSERT",X=EXECUTE ON PROCEDURE:"EXECUTE",S=SELECT ON:"SELECT",M=:"MEMBERSHIP"
TGrantOptionType:Values=Closed,0=,1=WITH GRANT OPTION,2=WITH ADMIN OPTION
TGrantUserType:Values=Closed,0=,1=VIEW,2=TRIGGER,5=PROCEDURE,8=USER,13=:"ROLE"
TRefUpdRule:Values1=Closed,RESTRICT=:"RESTRICT",CASCADE=ON UPDATE CASCADE:"CASCADE"
TRefUpdRule:Values2=SET NULL=ON UPDATE SET NULL:"SET NULL",SET DEFAULT=ON UPDATE SET DEFAULT:"SET DEFAULT"
TRefDelRule:Values1=Closed,RESTRICT=:"RESTRICT",CASCADE=ON DELETE CASCADE:"CASCADE"
TRefDelRule:Values2=SET NULL=ON DELETE SET NULL:"SET NULL",SET DEFAULT=ON DELETE SET DEFAULT:"SET DEFAULT"
TTriggerState:Values=Closed,0=ACTIVE,1=INACTIVE
Догадались, что это означает? Мы добавили ключевое слово FREE_IT для UDF, определили типы привилегий, типы Grant Option, классы пользователей для Grant Option и типы каскадных операций. Заметьте, что переводы строк для параметров не допускаются, т. е. возможно в Вашем браузере некоторые строки не поместились по ширине экрана.
К следующему абзацу после строки
INTRBASE:Objects8=O:C:0M::"Blob Filters":BlobFilter
добавьте определения объектов ролей и привилегий:
INTRBASE:Objects9=O:C:0M::"Roles":Role
INTRBASE:Objects10=O:C:0M::"Privileges":Privilege
Найдите группу строк
RDB$TRIGGERS:Name=RDB$TRIGGER_NAME
RDB$TRIGGERS:Attrs1=RDB$TRIGGER_TYPE=Type="Type"::ER:::VM=TTriggerType
RDB$TRIGGERS:Attrs2=RDB$TRIGGER_SEQUENCE=Position="Position"::ER
К атрибутам триггера нужно добавить атрибут State, т. е. ACTIVE/INACTIVE. Attr2 нужно изменить на Attr3, Attr1 на Attr2, и в качестве Attr1 добавить строку
RDB$TRIGGERS:Attrs1=RDB$TRIGGER_INACTIVE=State="State"::ER:::VM=TTriggerState
Строки с Attr2 и Attr3 будут выглядеть как
RDB$TRIGGERS:Attrs2=RDB$TRIGGER_TYPE=Type="Type"::ER:::VM=TTriggerType
RDB$TRIGGERS:Attrs3=RDB$TRIGGER_SEQUENCE=Position="Position"::ER
Т. е. здесь просто меняются номера Attrs.
Нельзя не упомянуть еще одну особенность. В IB Database ограничения полей (check constraints) создаются в виде триггеров. Поэтому если у таблицы есть хоть один check constraint, но ни одного вами определенного триггера, ветка triggers все равно будет раскрываемой и иметь один или несколько триггеров с именами CHECK_xxx. Если вы не хотите видеть check constraints как триггеры, то можете изменить строку
RDB$TRIGGERS:FilterSystemObjects=A.RDB$SYSTEM_FLAG <> 1 or A.RDB$SYSTEM_FLAG IS NULL
на строку (это должна быть одна длинная строка без переводов CRLF)
RDB$TRIGGERS:FilterSystemObjects=(A.RDB$SYSTEM_FLAG <> 1 or A.RDB$SYSTEM_FLAG IS NULL) and A.RDB$TRIGGER_NAME NOT STARTING WITH 'CHECK_'
Но при этом нужно избегать именования ваших триггеров как CHECK_xxx, потому что они будут "пропадать" после создания, т.к. фильтр будет скрывать такие триггеры как системные.
Найдите строки
RDB$REF_CONSTRAINTS:Attrs4=RDB$UPDATE_RULE="Update Rule" RDB$REF_CONSTRAINTS:Attrs5=RDB$DELETE_RULE="Delete Rule"
и замените их на
RDB$REF_CONSTRAINTS:Attrs4=RDB$UPDATE_RULE=UpdateRule="Update Rule"::R:::VM=TRefUpdRule
RDB$REF_CONSTRAINTS:Attrs5=RDB$DELETE_RULE=DeleteRule="Delete Rule"::R:::VM=TRefDelRule
Вместо :R: можно было бы поставить :ER:, но тогда этот параметр можно редактировать в закладке Definition при просмотре foreign key. Это неправильно, поскольку FK нельзя изменить – можно только удалить и создать заново.
После этих изменений в блоке строк, начинающихся с Foreign key, найдите строку
ForeignKey:Text:Get3=`\N` REFERENCES `=Table`
и добавьте после нее строку
ForeignKey:Text:Get4=`\N` `=UpdateRule` `=DeleteRule`
Это приведет к тому, что в объявлении foreign key будет выводиться информация о каскадных обновлениях или удалениях, если таковые были указаны при создании foreign key.
Перед началом раздела определений, откуда брать какие системные объекты, начинающемся со строки
Table:Definition=Root=RDB$RELATIONS
добавляем "разделы", т. е. отдельно стоящие наборы строк для ролей
RDB$ROLES:Name=RDB$ROLE_NAME
RDB$ROLES:Attrs=RDB$OWNER_NAME="Owner"
и для привилегий
RDB$USER_PRIVILEGES:Name=RDB$RELATION_NAME RDB$USER_PRIVILEGES:Attrs1=RDB$USER="User"
RDB$USER_PRIVILEGES:Attrs2=RDB$GRANTOR="Grantor"
RDB$USER_PRIVILEGES:Attrs3=RDB$PRIVILEGE=Privilege="Privilege"::E:::VM=TPrivilegeType
RDB$USER_PRIVILEGES:Attrs4=RDB$GRANT_OPTION=GrantOption="GrantOption"::E:::VM=TGrantOptionType
RDB$USER_PRIVILEGES:Attrs5=RDB$RELATION_NAME="Object"
RDB$USER_PRIVILEGES:Attrs6=RDB$USER_TYPE=UserType="UserType"::ER:::VM=TGrantUserType RDB$USER_PRIVILEGES:FilterSystemObjects=A.RDB$RELATION_NAME NOT STARTING WITH 'RDB$'
После строки
Dimension:Definition=Root=RDB$FIELD_DIMENSIONS
нужно добавить определения объектов Role и Privileges
Role:Definition=Root=RDB$ROLES
Privilege:Definition=Root=RDB$USER_PRIVILEGES
Privilege:Warning=Loss
Privilege:EAttrs=Text,New,Delete,Save
Privilege:Text:Get=GRANT `=Privilege` `N` TO `=UserType` `=User` `=GrantOption
Privilege:Text:RE=GRANT\_+(\w+)\_+(\w+)\_+(\w+)\_+(\w+)
Privilege:Delete:Text=REVOKE `=Privilege` `N` FROM `=UserType` `=User` Privilege:AttrReference:User=RDB$USER
Privilege:AttrReference:Grantor=RDB$GRANTOR Privilege:AttrReference:Privilege=RBD$PRIVILEGE
Privilege:AttrReference:Text1=select RDB$USER from RDB$USER_PRIVILEGES Privilege:AttrReference:Text2=where RDB$RELATION_NAME=`OQ`
Role:Warning=Loss
Role:EAttrs=Text,New,Delete,Save
Role:Text:Get=CREATE ROLE `N`
Role:Text:RE=CREATE ROLE
Role:Delete:Text=DROP ROLE `O`
Теперь расширяем возможности View. Найдите строку
View:Values=A.RDB$VIEW_SOURCE IS NOT NULL
и вместо строки, начинающейся с View:Objects (удалите эту строку целиком), вставьте строки
View:Objects1=T:P:0M:NMDR:"Columns":Column
View:Objects2=T:O:0M::"Triggers":Trigger
View:Objects3=T:O:0M::"Grants":Privilege
Вторая строка со словом Trigger как раз добавляет возможность просмотра редактирования триггеров для представлений.Ну и последнее, добавление отображения состояния триггера (active/inactive). Тут достаточно большой кусок, поэтому я приведу оригинальный набор строк:
Trigger:Text:Init1=CREATE TRIGGER `EDefaultName` FOR `{<}N`
Trigger:Text:Init2=AFTER UPDATE POSITION 0 AS`\N`BEGIN`\N`END
Trigger:Text:Get1=CREATE TRIGGER `N` FOR `{<}N`
Trigger:Text:Get2=`=Type` POSITION `=Position` `=Text`
Trigger:Text:RE=CREATE\_+TRIGGER\_+(\w+)\_+FOR\_+(\w+)\_+(\w+\_\w+)\_+POSITION\_+(\w+)\_+(.*)
Trigger:Text:Set=*N=1,2=`{<}N`,Type=3,Position=4,Text=5
Trigger:Delete:Text=DROP TRIGGER `O`
который надо просто заменить на
Trigger:Text:Init1=CREATE TRIGGER `EDefaultName` FOR `{<}N`
Trigger:Text:Init2=`\N`ACTIVE AFTER UPDATE POSITION 0 AS`\N`BEGIN`\N`END
Trigger:Text:Get1=CREATE TRIGGER `N` FOR `{<}N``\N`
Trigger:Text:Get2=`=State` `=Type` POSITION `=Position` `=Text`
Trigger:Text:RE=CREATE\_+TRIGGER\_+(\w+)\_+FOR\_+(\w+)\_+(\w+)\_+(\w+\_\w+)\_+POSITION\_+(\w+)\_+(.*)
Trigger:Text:Set=*N=1,2=`{<}N`,State=3,Type=4,Position=5,Text=6
Trigger:Delete:Text=DROP TRIGGER `O`
Для показа прав на процедурах также можно в соответствующем блоке определения Procedure добавить строку
Procedure:Objects2=T:O:0M::"Grants":Privilege
Вот и все.