ЛАСУ ТРИНИТИ, г. Троицк
тел. (095) 334-0408
Несколько "хакерских" способов русификации Oracle
В статье С. Мосина [Oracle Magazine/Russian Edition номер 1 за лето 1996 -- прим. webmaster] обсуждались способы корректной установки поддержки национальных кодировок и стандартов в продуктах Oraclе. Несомненно при инсталяции крайне желательно выбирать "Русский", даже если это пробная установка. Однако, как показывает практика, часто у нас еще встречаются СУБД Oracle с установленной CHARSET=WE8ISO8859P1 по умолчанию. Иногда это делается по незнанию, чаще с мыслью, что эта инсталляция временна, никогда не поздно будет изменить, а порой некоторые версии, например, распространявшаяся через Интернет trial версия Personal Oracle for Windows 3.1, не имели локальных кодировок.. Потом разрабатывались приложения, вводились данные, система росла, и неудобства, типа перевода (а точнее не перевода) в другой регистр русских 'Ч' и 'Я' игнорировались или корректировались написанием локализованных версий функций RLOWER() и RUPPER(). Реальные проблемы возникали, когда система переносилась на платформу с другой основной кодировкой, или нужно было реэкспортировать в такую систему несколько таблиц для проведения демонстрации. Вроде пустячок, а неприятно, когда на месте обещанной информации возникают сообщения вроде '?????????'. К сожалению Oracle Corp. не предусмотрела легальных (документированных) способов изменения кодировок СУБД и приложений, поэтому приходиться "изобретать велосипед". Поскольку мной было потрачено довольно много времени на решение этой проблемы, я надеюсь, что приведенные здесь советы кому-то действительно помогут.
Стоит сразу оговорится, что описанные здесь методы применялись на версиях 7.1.4 - 7.2.2 для Windows 95, Windows NT, SCO Unix и судя по положительным отзывам в конференции fido7.ru.rdbms.oracle 7.3 for HP/UX. Тем не менее (или тем более) я бы рекомендовал перед их использованием сделать full export (backup).
1. Изменение DATABASE CHARSET
Следует заметить, что перекодировок данных здесь не происходит, изменяется только пометка для Oracle. Вы должны точно представлять в какой кодировке ваши данные находятся. Если Вы хотите попутно и +перекодировать данные используйте Метод 2 - он более легален, применим ко всем версиям, хотя он и значительно более трудоемкий.
Прежде, чем использовать этот метод проверьте, присутствует ли необходимая вам кодировка в списке допустимых :
SQL> select value from V$NLS_VALID_VALUES
2 where parameter='CHARACTERSET'
3 and (value like 'RU%' or value like 'CL%') 4
/
VALUE
----------------------
CL8ISO8859P5
RU8PC866
RU8BESTA
RU8PC855
CL8MACCYRILLIC
CL8MACCYRILLICS
CL8MSWIN1251
CL8EBCDIC1025
CL8EBCDIC1025X
CL8BS2000
Кроме этих Вам могут встретиться или понадобиться CLKOI8R, BG8MSWIN(болгарская) и другие. Oracle Corp. позаботилась если не обо всех ситуациях, то по крайней мере о многих. Если Вам интересно, что представляет из себя та или иная кодировка, воспользуйтесь примером из пункта 4.
Текущие установки NLS БД можно просмотреть используя view NLS_DATABASE_PARAMETERS. Что представляет из себя это view?
SQL> select text from dba_views where view_name='NLS_DATABASE_PARAMETERS';
TEXT
--------------------------------------------------------------------------
select name,
substr(value$, 1, 30)
from props$
where name like 'NLS%'
PROPS$ - это fixed table, где хранится информация об NLS и не только. Несмотря на термин fixed на многих версиях Oracle позволяет производить UPDATE над ней (хотя не во всех - какие-то версии Personal Oracle не допускают этого). Допустим Вы имеете WE8ISO8859P1, а Вам нужно CL8MSWIN1251.
SQL> select * from props$ where name='NLS_CHARACTERSET';
NAME VALUE$ COMMENT$
---------------- --------------- --------------------
NLS_CHARACTERSET WE8ISO8859P1 Character set
SQL> update props$ set VALUE$='CL8MSWIN1251' where name='NLS_CHARACTERSET';
1 row updated.
SQL> select * from props$ where name like 'NLS_CHARACTERSET';
NAME VALUE$ COMMENT$
---------------- --------------- --------------------
NLS_CHARACTERSET CL8MSWIN1251 Character set
SQL> commit;
Commit complete.
И после этого остается только поменять переменную среды NLS_LANG на сервере и клиентах и спокойно наслаждаться видом неизвращенных "Я" и "Ч" как больших так и маленьких.
2. Использование Export - Import
Если Вы делаете экспорт в системе с кодировкой WE8ISO8859P1, а импорт на системе с русской кодировкой (или наоборот) Вы с удивлением (по крайней мере первый раз) обнаруживаете вместо русских букв сплошные вопросы (как на экране, так и вашей голове). Использование параметра CHARSET применимо для импорта данных из версий ранее 7 (Oracle 5, 6). Экспортный файл версии 7 содержит в себе информацию о кодировке данных, что и приводит к образованию вопросов. Как же этого избежать?
Ответ заключается всего в одном байте экспортного файла, а именно - в третьем (точнее в двух байтах - втором и третьем, но в наших случаях второй байт равен 0x00), где и находится идентификатор CHARSET.
Таблица 1. Идентификаторы CHARSET.
Кодировка | CS_ID(hex) | CS_ID(dec) | NLS RTL 3.1 Windows | NLS RTL 3.2 Windows | модуль из libnlsrtl.a * |
US7ASCII | 0x01 | 1 | lx20001.d | lx20001.nlb | lic001.o |
WE8ISO8859P1 | 0x1F | 31 | x2001F.d | lx2001F.nlb | lic031.o |
CL8ISO8859P5 | 0x23 | 35 | lx20023.d | lx20023.nlb | lic035.o |
RU8PC866 | 0x98 | 152 | lx20098.d | lx20098.nlb | lic152.o |
RU8BESTA | 0x99 | 153 | lx20099.d | lx20099.nlb | lic153.o |
RU8PC855 | 0x9B | 155 | lx2009B.d | lx2009B.nlb | lic155.o |
CL8MACCYRILLIC | 0x9E | 158 | lx2009E.d | lx2009E.nlb | lic158.o |
CL8MACCYRILLICS | 0x9F | 159 | lx2009F.d | lx2009F.nlb | lic159.o |
CL8MSWIN1251 | 0xAB | 171 | lx200AB.d | lx200AB.nlb | lic171.o |
CL8EBCDIC1025 | 0xB9 | 185 | lx200B9.d | lx200B9.nlb | lic185.o |
CL8EBCDIC1025X | 0xBA | 186 | lx200BA.d | lx200BA.nlb | |
CL8BS2000 | 0xEB | 235 | lx200EB.d | lx200EB.nlb | lic235.o |
*- для Oracle 7.1.4 for SCO Unix (в других версиях может отличаться)
Так что, все что нужно сделать -- это изменить этот байт с помощью любого шестнадцатеричного редактора.
3. Изменение кодировки приложений разработанных с помощью Developer/2000
Принцип изменения кодировки Forms(.FMB), Menu (.MMB), Reports (.RDF) одинаков :
1) преобразовать Binary-to-Text (NLS_LANG=WE8ISO8859P1)
2) исправить CHARSET с помощью редактора
Поскольку данные выбираются из БД и "перекодируются на лету" необходимо изменить только кодировку константных строк, которые в текстовых вариантах модулей описаны как
DESCRIBE ROSSTRINGS
BEGIN
...
UB2 cs
...
END
В действительности нужно заменить "cs = 31" на "cs = 171" (для кодировки отличной от CL8MSWIN1251, см. Табл 1).
3) изменить NLS_LANG=CL8MSWIN1251
4) преобразовать Text-to-Binary
Для корректности можете просмотреть следующие объекты(и их свойства) VG_COLOR(NAME_SET), VG_FONT(NAME_SET, CHAR_SET), и только для Reports - SRW2_DISPLAY_TAG(CHARSET), хотя мне не встречались русские названия цветов и фонтов.
4. Принцип перекодировки "на лету"
Для перекодировок Oracle NLS использует четыре таблицы:
struct NLS {
unsigned char t_UPPER[256];
unsigned char t_LOWER[256];
unsigned int t_TOUNI[256];
unsigned char t_TOLOC[1000];
/* для Windows размер этого массива 2000, но учитывая, что русские символы UNI находятся в первой тысяче этой таблицы, и что в некоторых версиях, например в моем Oracle 7.1.4 for SCO размер его действительно равен 1000, я рекомендую описывать структуру NLS указанным способом */
};
char UPPER(char c) { return( t_UPPER[ c ] ); }
char LOWER(char c) { return( t_LOWER[ c ] ); }
char Translate(char c, struct NLS * FROM, struct NLS * TO)
{ return( TO->t_TOLOC[ FROM->t_TOUNI[ c ] ] ); }
Естественно вышеприведенный С - пример весьма условен (он справедлив только для одно-байтных символьных наборов). Тем не менее он наглядно представляет алгоритмы работы NLS.
Расположение NLS-таблиц зависит от ОС, например, для Windows xx - это
%ORACLE_HOME%\NLSRTL??\DATA\
Теоретически Вы можете разработать собственную кодировку используя одну из существующих NLS-структур. Практически я делал это. Мне попалась самопальная кодировка на VAX/VMS, где 66 русские буквы были расположены в диапазоне с 0xBD по 0xFE. Я решил, что CHARSET RU8PC855 мне не доведется использовать, извлек lic155.o из libnlsrtl.a, перебил таблицы и добавил в библиотеку снова. В итоге вместо выгрузки данных в текстовый файл, перекодирования его, пересоздания таблиц, описания .CTL-файлов и загрузки в другую БД, я использую просто экспорт-импорт (CHARSET=RU8PC855).
И напоследок я привожу обещанный выше пример. При запуске он использует
два параметра: showNLS
/* Show Oracle NLS table */
#include
struct NLS {
unsigned char t_UPPER[256];
unsigned char t_LOWER[256];
unsigned int t_TOUNI[256];
unsigned char t_TOLOC[2000];
};
struct NLS_FILE {
int lbl; /* NLS label 0x5A5A */
char un1[10];
int fl; /* File length */
int nl; /* Coding name length */
char un2[84];
int cc; /* Current code oxAB for CL8MSWIN1251 */
int bc; /* Oracle Language Base Coding - 0x23 for Russian */
char un3[8];
struct NLS nls;
char un4[14];
int nl2; /* Coding name length ( nl2 == nl ) */
} BT, ST;
char bn[20], sn[20];
int Translate(int c, struct NLS * FROM, struct NLS * TO )
{ return( TO->t_TOLOC[ FROM->t_TOUNI[ c ] ] ); }
main(
int cp, char **pp) { int i,j, c;
FILE * bf, *sf;
if(cp!=3) { puts("Run: showNLS
if((bf=fopen(pp[1],"rb"))==NULL)
{ printf("Error: Base table open: %s\n",pp[1]); exit(2); }
fseek( bf, -1l, SEEK_SET );
fread( &BT, sizeof(BT), 1, bf );
if( BT.lbl != 0x5A5A )
{ printf("Error: File %s isn\'t Oracle NLS file\n",pp[1]); fclose(bf); exit(3); } f
seek( bf, (long) -BT.nl, SEEK_END );
fread( bn, BT.nl, 1, bf );
if((sf=fopen(pp[2],"rb"))==NULL)
{ printf("Error: Show table open: %s\n",pp[2]); fclose(bf); exit(4); }
fseek( sf, -1l, SEEK_SET );
fread( &ST, sizeof(ST), 1, sf );
if( ST.lbl != 0x5A5A )
{ printf("Error: File %s isn\'t Oracle NLS file\n",pp[2]); fclose(bf); fclose(sf); exit(5); }
fseek( sf, (long) -ST.nl, SEEK_END );
fread( sn, ST.nl, 1, sf );
if( ST.bc!=BT.bc ) {
printf("Error: Oracle Base Coding for %s and %s don't equal",bn, sn );
fclose(bf); fclose(sf); exit(6);
}
printf("Table: %s\n\n",sn );
for(i=0;i<16;i++) {if(i%8==0) printf(" |"); printf(" %X",i); } printf("\n");
for(i=0;i<16;i++) {if(i%8==0) printf(" --+"); printf("--"); } printf("\n");
for(j=0;j<16;j++) {
for(i=0;i<16;i++) {
if(i%8==0) printf(" %X |",j);
c=Translate( i*16+j, &ST.nls, &BT.nls );
printf(" %c",c<32?32:c);
}
printf("\n");
}
fclose(sf);
fclose(bf);
}