| iBase.ru | |||||||
| О компании | Обучение | Техподдержка | Ремонт БД | Купить Delphi | InterBase | Firebird | Документация |
12.12.2008, last updated 13.11.2011
составитель: kdv, www.ibase.ru
информация предоставлена: dimitr, hvlad
благодарности:
shmel, Janex
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
Windows - все последние, кроме 95, 98, ME. В этих ОС поддержка unicode неполная, поэтому, в частности, утверждается, что Delphi 2009 и приложения, на ней написанные, не будут работать в Windows 95, 98 и ME.
Linux - см. unicode faq.
Для начала нужна база с поддержкой 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, если вы не укажете другую специально.
Есть несколько вариантов:
Да, причем зависит от самих символов, которые находятся в строках.
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.
UNICODE_FSS реализует более старую версию стандарта Unicode, которая ограничена 3-мя байтами на символ (поэтому, теоретически, поддерживает не все языки), для нее нет алфавитных наборов сортировок, в версиях ниже Firebird 2.5 для нее не проверяется корректность введенного текста с точки зрения unicode.
UTF8 поддерживает последнюю версию стандарта Unicode, до 4 байт на символ, все вышеперечисленные недостатки отсутствуют.
InterBase - с 2007
Firebird - с 2.0
При этом, разумеется, база данных с поддержкой UTF8 может быть создана только в этих версиях, поскольку UTF8 поддерживается не только кодом сервера, но и таблицей rdb$character_sets в базе данных.
Например, если взять базу данных от Firebird 1.5, и открыть 2.0, то поддержки UTF8 в этой базе данных не будет. Чтобы возможность использовать в БД UTF8 появилась, нужно ей сделать backup и restore (при restore будет создана новая БД в новом формате со старыми данными).
Только если вы не предполагаете переходить на современные версии InterBase и Firebird, которые поддерживают UTF8. Недостатки UNICODE_FSS перечислены в предыдущих пунктах.
Разумеется, для этого достаточно указать чарсет соединения 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)
а, А, б, Б, в, В, ...
Помните, что при сортировке строк значение также имеет длина строки. Поэтому в последнем случае, не смотря на то что по одной букве б идет перед Б, строка "бб" будет идти после строки "Б".
Есть 4 сортировки
UTF8 - по умолчанию, бинарная сортировка
UCS_BASIC - то же самое
UNICODE - алфавитная сортировка
UNICODE_CI - алфавитная сортировка для регистронечувствительного поиска.
пример сортировки смотрите выше.
Нормально. Для русских букв таблица перевода прописных в строчные (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).
Это значит, что данные, которые передаются на сервер, идут не в юникоде, а в какой-то другой кодировке. То есть виноват драйвер, компоненты доступа, или само приложение.
Да, можно, но при этом нужно учитывать, что кодировка должна быть указана для любых строковых данных, как констант, так и параметров, передаваемых в запросе. Например, если сделать попытку выполнить запрос из предыдущего примера
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, или в новых версиях, где при выборе чарсета коннекта 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 и строковой константы.
Это можно сделать только копированием данных из одной базы в другую:
К скриптам есть несколько требований.
В начале скрипта до коннекта к БД должно быть указание SET NAMES ...;, совпадающее с кодировкой символов, которые используются в скрипте.
Например, если текстовый файл содержит символы 1251, то тогда в начале скрипта должно быть
SET NAMES WIN1251;
Если текстовый файл содержит символы в unicode, то тогда в начале скрипта должно быть написано
SET NAMES UTF8;
При этом, сам файл должен содержать символы именно в юникоде. Например, для "Блокнот"а (notepad) это делается следующим образом - открываете текстовый файл, Сохранить как - выбираете кодировку UTF-8 (по умолчанию текстовые файлы Блокнот создает в ansi, если там явно нет символов unicode).
После этого национальные символы, вводимые при редактировании этого файла в Блокноте, будут в UTF8.
Баг зарегистрирован в 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;
К сожалению, нет. Причем, "неодинаковых" аспектов много.
В 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.
При написании 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.
Да, поскольку практически все известные старые библиотеки функций (rfunc, freeudflib и т.п.) обрабатывали строки как однобайтные наборы символов, без учета кодировок вообще. А российские разработчики писали свои udf, предполагая что кодировка базы будет всегда или none или win1251. Поэтому, разумеется, все эти функции не умеют работать с unicode (многобайтными кодировками).
Так что, нужные функции придется переписывать, как в примере выше.
Временно решить проблему, если первое время в базе в utf8-строках будет хранится только информация, полученная из 1251, можно объявив такие udf с указанием для их строковых параметров character set win1251.
В этом случае сервер автоматически перекодирует utf8 в 1251 и обратно при вызове udf. Однако, как только в такую udf будут переданы символы, несовместимые с 1251, сервер сообщит об ошибке.