Игорь Филимонов

ЛАСУ ТРИНИТИ, г. Троицк

тел. (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];

};

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\ (см. Табл.1), для Oracle 7.1.4 for SCO Unix - %ORACLE_HOME%\lib\libnlsrtl.a (каждая кодировка находится в .о модуле этой библиотеки).

Теоретически Вы можете разработать собственную кодировку используя одну из существующих NLS-структур. Практически я делал это. Мне попалась самопальная кодировка на VAX/VMS, где 66 русские буквы были расположены в диапазоне с 0xBD по 0xFE. Я решил, что CHARSET RU8PC855 мне не доведется использовать, извлек lic155.o из libnlsrtl.a, перебил таблицы и добавил в библиотеку снова. В итоге вместо выгрузки данных в текстовый файл, перекодирования его, пересоздания таблиц, описания .CTL-файлов и загрузки в другую БД, я использую просто экспорт-импорт (CHARSET=RU8PC855).

И напоследок я привожу обещанный выше пример. При запуске он использует два параметра: showNLS , где -- файл опорной кодировки, для запуска в DOS-окне - lx0098.d (подразумевается, что Вы используете NLS v 3.1), -- файл кодировки, которую вы хотите просмотреть. С помощью этой программы можно посмотреть, что представляет из себя какая-нибудь "диковинная" кодировка (вроде RU8PC855, с точки зрения пользователя MS Windows) или увидеть, чем отличаются CL8KOI8R и RU8BESTA.

/* 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 "); exit(1); }

  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);

}