iBase.ru
О компании Обучение Техподдержка Ремонт БД Купить Delphi InterBase Firebird Документация

Unicode FAQ - InterBase, Firebird, Delphi 2009, 2010, C++Builder 2009, 2010

12.12.2008, last updated 13.11.2011
составитель: kdv, www.ibase.ru
информация предоставлена: dimitr, hvlad
благодарности: shmel, Janex

Unicode. Что это и зачем это нужно?

Unicode - это способ представления разных национальных символов в универсальном формате. На вопрос "зачем это нужно" можно ответить следующим образом - если вы не планируете переводить ваши приложения на другие национальные языки (немецкий, французский и т.п.), то unicode вам не нужен. В этом случае вы спокойно используете базу данных в WIN1251, с аналогичным параметром коннекта, и работаете как и раньше.

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

О unicode вообще
Поддержка Unicode в Delphi 2009 и C++Bulder 2009 - часть 1, часть 2, часть 3
Юникод в Delphi 2009
Перечень проблем, которые могут возникнуть при переносе кода на Delphi 2009, 2010 или XE с предыдущих версий.

Также крайне рекомендуется для чтения электронные книги, доступные бесплатно покупателям Delphi 2009, 2010 и XE:

Marco Cantu - Delphi 2009 Handbook PDF eBook

Marco Cantu - Delphi 2010 Handbook

Какие операционные системы поддерживают UNICODE?

Windows - все последние, кроме 95, 98, ME. В этих ОС поддержка unicode неполная, поэтому, в частности, утверждается, что Delphi 2009 и приложения, на ней написанные, не будут работать в Windows 95, 98 и ME.

Linux - см. unicode faq.

Что нужно для работы с unicode?

Для начала нужна база с поддержкой unicode. Firebird 2.0 и InterBase 2007 поддерживают кодировку UTF-8, которая является полноценной реализацией формата для хранения unicode-символов.

Далее, потребуется среда разработки и компоненты или драйверы, поддерживающие unicode.
Первые среды разработки с полной поддержкой unicode - Delphi 2009 и C++Builder 2009. Если вы пока еще используете предыдущие версии Delphi и BCB, то использование unicode будет проблематичным - вам придется не только использовать специальные компоненты (вроде TMS), но и компоненты доступа к БД или драйверы, которые также поддерживают unicode.

Как создать базу в юникоде?

Для этого достаточно при создании БД указать character set UTF8. После этого по умолчанию все создаваемые строковые поля и переменные (varchar, char) таблиц, процедур, триггеров и т.д. будут иметь кодировку UTF8, если вы не укажете другую специально.

Как сохранять данные в разных кодировках?

Есть несколько вариантов:

  1. Старый и неудобный: можно делать с любыми версиями InterBase и Firebird: создаете столбец в таблице, указывая нужный character set, затем при подсоединении к БД указываете этот же чарсет. Не очень удобно, потому что для каждого языка (чарсета) приложение должно показывать пользователю свой "набор" строковых столбцов.
  2. Современный и простой: Создать базу в UTF8, и сохранять данные либо в конкретном character set, либо сразу передавать данные в UTF8. Для передачи данных на сервер в UTF8 нужно, чтобы драйверы или компоненты доступа делали это "прозрачно" для приложения. См. пункт.

А говорят, что в unicode строки занимают больше места?

Да, причем зависит от самих символов, которые находятся в строках.
UTF8 это формат с плавающим размером символа, от 1 до 4 байт. В частности, символы русских букв в 1251 занимают по 2 байта, а "стандартные" английские буквы с кодом от 32 до 127 занимают 1 байт. Пример:
Создадим таблицу со столбцом varchar(30) в win1251, и еще таблицу со столбцом varchar(30) в utf8. Зальем в первую таблицу 100 тысяч записей, со случайными символами в кодировке win1251 от А до я, длиной от 10 до 30 символов. Затем перельем (insert into ... select from) эти данные во вторую таблицу. При этом данные корректно отконвертируются из 1251 в utf8. В итоге, обе таблицы в IBAnalyst будут показаны как:

Table Records RecLength VerLen Versions Max Vers Data Pages Size, mb Slots Avg fill% RealFill
X1251 100000 28.86 0.00 0 0 852 6.66 852 66 66
XUTF8 100000 49.01 0.00 0 0 1094 8.55 1094 74 74

интерпретировать результат можно следующим образом:

Так что, в зависимости от кодировки, данные могут занимать в БД разный объем.

Чем отличаются кодировки UNICODE_FSS и UTF8?

Это несколько разные реализации поддержки unicode.

UNICODE_FSS реализует более старую версию стандарта Unicode, которая ограничена 3-мя байтами на символ (поэтому, теоретически, поддерживает не все языки), для нее нет алфавитных наборов сортировок, в версиях ниже Firebird 2.5 для нее не проверяется корректность введенного текста с точки зрения unicode.

UTF8 поддерживает последнюю версию стандарта Unicode, до 4 байт на символ, все вышеперечисленные недостатки отсутствуют.

С каких версий InterBase и Firebird поддерживают UTF8?

InterBase - с 2007

Firebird - с 2.0

При этом, разумеется, база данных с поддержкой UTF8 может быть создана только в этих версиях, поскольку UTF8 поддерживается не только кодом сервера, но и таблицей rdb$character_sets в базе данных.

Например, если взять базу данных от Firebird 1.5, и открыть 2.0, то поддержки UTF8 в этой базе данных не будет. Чтобы возможность использовать в БД UTF8 появилась, нужно ей сделать backup и restore (при restore будет создана новая БД в новом формате со старыми данными).

А можно использовать UNICODE_FSS вместо или если нет UTF8?

Только если вы не предполагаете переходить на современные версии InterBase и Firebird, которые поддерживают UTF8. Недостатки UNICODE_FSS перечислены в предыдущих пунктах.

Я могу работать с базой в UTF8 через WIN1251?

Разумеется, для этого достаточно указать чарсет соединения WIN1251. Данные будут идти на сервер в 1251, и автоматически перекодироваться в UTF8 при сохранении (при чтении - перекодироваться обратно в win1251). Это самый легкий вариант начала работы с юникодом. Также это подходящий вариант, если используете Delphi ниже версии 2009, и вы не хотите использовать никакие компоненты unicode (например tms), но планы перехода на unicode есть.

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

А как сортируются данные в юникоде?

У кодировки UTF8 есть два набора сортировок (collate)- бинарная (UTF8, по умолчанию), и алфавитная (UNICODE). Пользоваться ими можно точно так же, как и с любыми другими наборами сортировок - или указывать collate в объявлении столбца таблицы, или указывать collate в order by. Примеры есть в FAQ по работе с win1251.

Пример порядка сортировки русских букв столбца UTF8 с умолчательным collate (точно так же как win1251)

А, Б, В, ...а, б, в...

Пример порядка сортировки русских букв столбца UTF8 с collate UNICODE (order by ... collate UNICODE - эквивалент order by ... collate pxw_cyrl)

а, А, б, Б, в, В, ...

Помните, что при сортировке строк значение также имеет длина строки. Поэтому в последнем случае, не смотря на то что по одной букве б идет перед Б, строка "бб" будет идти после строки "Б".

Какие есть сортировки (collate) для UTF8?

Есть 4 сортировки

UTF8 - по умолчанию, бинарная сортировка
UCS_BASIC - то же самое
UNICODE - алфавитная сортировка
UNICODE_CI - алфавитная сортировка для регистронечувствительного поиска.

пример сортировки смотрите выше.

Как работает upper в юникоде?

Нормально. Для русских букв таблица перевода прописных в строчные (upper) и обратно (lower) работает как для UTF8 так и для collate UNICODE. То есть, указывать collate для столбцов при вызове upper/lower не нужно.

Регистронезависимый поиск

Можно организовать двумя способами

По старинке

при помощи upper, даже если столбцы не имеют collate unicode/unicode_ci. Например

select * from table
where upper(name) = 'СТРОКА'

Естественным для unicode образом

С collate unicode_ci можно проще:

1. объявляем столбец как

 name varchar(30) collate unicode_ci

2. делаем поиск как

select * from table
where name = 'строка'

при этом, например, если есть записи с 'а' и 'А', то они будут выданы все независимо от того, в строке поиска указано 'а' или 'А'. То есть, поиск получается регистронезависимым. Более того - если есть индекс по столбцу name collate unicode_ci, то он будет использован оптимизатором для поиска.

Если же столбец уже создан и не имеет collate (или имеет collate unicode), то "превратить" его в регистронечувствительный можно указанием collate unicode_ci

select * from table
where name collate unicode_ci = 'строка'

однако если уже есть индекс по name, то он использоваться не будет.

примечание: пока существует только одна проблема с unicode_ci - если для столбца указан этот collate, то по нему будет нельзя создать FK (CORE-1989).

Я создал базу и таблицы в UTF8, подсоединился в UTF8, и получаю ошибку Malformed string

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

Можно ли использовать при коннекте чарсет NONE?

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

select * from table 
where name collate unicode_ci = 'строка' 

с чарсетом коннекта NONE, то будет выдана ошибка Malformed string. Обратите внимание, что речь идет именно о символах win1251, т.е. тех, которые в однобайтовой кодировке имеют код больше 127, и в unicode кодируются двумя байтами на символ. Если заменить 'строка' на 'string', то ошибки не будет.

Поэтому, если константа действительно содержит символы кодировки 1251, то это нужно явно указать в запросе

select * from table 
where name collate unicode_ci = _win1251 'строка'   

А почему IBExpert тоже дает ошибку Malformed sting?

Такое может быть в старых версиях IBExpert, или в новых версиях, где при выборе чарсета коннекта UTF8 рядом не снята галочка с пункта "Do NOT perform conversion from/to UTF8.

Поэтому в новой версии IBExpert перед коннектом к БД в utf8 в Database Registration Info сначала выключите указанную галку, затем делайте соединение. Для старых версий IBExpert есть варианты:

Написать запрос в SQL Editor, затем в меню по правой кнопке мыши вызвать Convert to UNICODE, и только затем выполнить запрос.

Или - перед строковыми константами в запросе нужно указывать явно их кодировку. Например

select * from table   
where name collate unicode_ci = _win1251 'строка'

Также это будет работать, если чарсет коннекта указан как none. При чарсете коннекта win1251 запрос уйдет на сервер в корректной кодировке, и данные будут корректно преобразованы сервером из win1251 в utf8 для сравнения name и строковой константы.

Как мне сконвертировать базу WIN1251 в UTF8?

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

  1. создать новую базу в UTF8
  2. извлечь скрипт метаданных, убрать оттуда все упоминания WIN1251 (помните - у вас могут быть проблемы с отсуствием используемых сортировок в UTF8), и применить этот скрипт на базе в UTF8
  3. перенести все данные каким-нибудь инструментом, вроде IBPump. Такому инструменту не надо уметь поддерживать юникод, т.к. при записи данных в чарсете коннекта win1251 сервер сам преобразует их в UTF8.

Какую кодировку использовать в скриптах?

К скриптам есть несколько требований.

В начале скрипта до коннекта к БД должно быть указание SET NAMES ...;, совпадающее с кодировкой символов, которые используются в скрипте.
Например, если текстовый файл содержит символы 1251, то тогда в начале скрипта должно быть

SET NAMES WIN1251;

Если текстовый файл содержит символы в unicode, то тогда в начале скрипта должно быть написано

SET NAMES UTF8;

При этом, сам файл должен содержать символы именно в юникоде. Например, для "Блокнот"а (notepad) это делается следующим образом - открываете текстовый файл, Сохранить как - выбираете кодировку UTF-8 (по умолчанию текстовые файлы Блокнот создает в ansi, если там явно нет символов unicode).

После этого национальные символы, вводимые при редактировании этого файла в Блокноте, будут в UTF8.

Ошибка Division by zero в IBX Delphi 2009

Баг зарегистрирован в QualityCentral под номером 68103.

Причина - в кривом методе

function TIBXSQLVAR.GetCharsetSize: Integer; 

модуля IBSQL.

В IBX до версии, поставляемой с Delphi, до сих пор не было вообще никакой поддержки unicode. В IBX 2009 этот метод появился с целью поддержки unicode, но идентификаторы кодировок InterBase в нем зашиты жестко, что приводит к несовместимости с идентификаторами кодировок в Firebird (см. отличия в поддержке UTF8), а также к некорректной обработке SQLSubtype, где и у InterBase (!) и у Firebird в старшем байте может содержаться код collate столбца. Более правильно было бы определять количество байт на символ обращаясь к столбцу RDB$BYTES_PER_CHARACTER таблицы RDB$CHARACTER_SETS. Это не только унифицировало бы поддержку InterBase и Firebird, но и обеспечило бы совместимость IBX со всеми будущими версиями InterBase, если бы в них появлялись новые кодировки. Однако такая реализация потребовала бы "кэширования" данной информации, чтобы не происходило обращение к этим таблицам каждый раз при вызове GetCharsetSize.

Пример скорректированного для Firebird кода GetCharsetSize (модуль IBSQL.pas):

function TIBXSQLVAR.GetCharsetSize: Integer;
begin
  case SQLVar.SQLSubtype and $FF of // здесь and $FF убирает id collate, возвращаемый Firebird
    0, 1, 2, 10, 11, 12, 13, 14, 19, 21, 22, 39,
    45, 46, 47, 50, 51, 52, 53, 54, 55, 58 :  Result := 1;
    5, 6, 8, 44, 56, 57, 64 : Result := 2;
    3 : Result := 3;
    4, 59 : Result := 4; // здесь правильно обрабатывается id UTF8 в Firebird
 else
   Result := 0;
 end;
end;

Замечание от 28.01.2010: В Delphi 2010 в этой функции есть небольшие исправления, в частности SQLSubtype and $FF, но идентификатор utf-8 Firebird (код 4) так и не поддерживается. Поэтому Вам все равно придется исправлять строку
59 : Result := 4;
на
4, 59 : Result := 4;

Поддержка UTF8 в InterBase и Firebird одинаковая?

К сожалению, нет. Причем, "неодинаковых" аспектов много.

В rdb$character_sets кодировка UTF8 имеет в Firebird идентификатор 4 (это был свободный номер рядом с давно имеющейся в IB и FB кодировкой UNICODE_FSS с ID = 3), а в InterBase - 59. Код 59 в Firebird имеет кодировка WIN1256. То есть, разработчики InterBase не ставили перед собой вопрос обеспечения совместимости с Firebird в этом плане.

Начиная с Firebird 1.5 при получении строковых данных CHAR и VARCHAR с сервера, если это не кодировки NONE или OCTETS, и если кодировка коннекта не NONE, то в sqlsubtype передается: в старшем байте код collate, а в младшем - код character set.

Как использовать Unicode (UTF8) в UDF?

При написании UDF на Delphi для работы с UTF8 есть несколько условий

Вот пример функции, которая принимает UTF8 строку, добавляет к ней текст в unicode, и возвращает UTF8 строку:

 DECLARE EXTERNAL FUNCTION utf8ex 
 CSTRING(80)
 RETURNS CSTRING(80) FREE_IT
 ENTRY_POINT 'utf8example'  MODULE_NAME 'utf8func'
uses ib_util;
...


function utf8example(P1: PAnsiChar): PAnsiChar; cdecl; export;
// параметры объявлены как PAnsiChar, потому что теперь в D2009/2010 обычный 
// PChar это PWideChar, что потребовало бы дополнительного приведения типов
var u: UTF8String; // переменная для работы с unicode в формате utf-8 в Delphi
begin
  u:=UTF8String(p1) + 'привет'; // в u получаем склеенную строку в UTF8
  Result:=ib_util_malloc(Length(u)+1); // Length(u) вычислит правильный размер строки, плюс байт для #0
  StrCopy(Result, PAnsiChar(u)); // копируем utf8 строку в переменную результата
end; 

Можно было бы использовать и другой тип строки для переменной u, но это потребует дополнительного приведения результата к UTF8String. Этот пример дан как наиболее простой для Delphi 2009/2010.

примечание: Если мы создали эту функцию в базе данных, у которой чарсет по умолчанию задан UTF8, то она (udf) будет работать корректно даже если ей на вход передать данные в кодировке 1251 или любой другой. Поскольку мы не задавали чарсет параметров, он будет идентичен умолчательному, то есть UTF8, и данные в любой другой кодировке будут автоматически перекодированы сервером как перед передачей в виде параметра, так и после получения результата от функции.
Если же создать эту функцию в базе данных с любым другим чарсетом по умолчанию, то эта функция работать не будет, если только при ее объявлении не указать у параметров CHARACTER SET UTF8.

Интересующимся работой с unicode в Delphi рекомендуем книгу Марко Канту Delphi 2009 Handbook, которая доступна в виде pdf бесплатно для зарегистрированных пользователей Delphi 2009 и 2010.

Есть проблемы со старыми UDF в базе UTF8

Да, поскольку практически все известные старые библиотеки функций (rfunc, freeudflib и т.п.) обрабатывали строки как однобайтные наборы символов, без учета кодировок вообще. А российские разработчики писали свои udf, предполагая что кодировка базы будет всегда или none или win1251. Поэтому, разумеется, все эти функции не умеют работать с unicode (многобайтными кодировками).

Так что, нужные функции придется переписывать, как в примере выше.
Временно решить проблему, если первое время в базе в utf8-строках будет хранится только информация, полученная из 1251, можно объявив такие udf с указанием для их строковых параметров character set win1251.

В этом случае сервер автоматически перекодирует utf8 в 1251 и обратно при вызове udf. Однако, как только в такую udf будут переданы символы, несовместимые с 1251, сервер сообщит об ошибке.


(c) iBase.ru, 2008-2010.
Запрещается перепечатка, перевод и копирование. Разрешается частичное цитирование с обязательной ссылкой на источник - www.ibase.ru/unicode_faq.html