28 окт. 2019 г.

Delphi & PostgreSQL. Фиксим "character with byte sequence 0xcc 0x81 in encoding "UTF8" has no equivalent in encoding "WIN1251""

В логах моей программы появилась странная ошибка:
25.10.2019 10:01:24.523 Thread #5: character with byte sequence 0xcc 0x81 in encoding "UTF8" has no equivalent in encoding "WIN1251"
Первой мыслью было, что Elasticsearch не принял данные, которые я ему передал REST-запросом. Но запустив программу под отладкой я получил эту ошибку при открытии запроса к БД PostgreSQL:
Project XYZ.exe raised exception class EPgError with message 'character with byte sequence 0xcc 0x81 in encoding "UTF8" has no equivalent in encoding "WIN1251"'
Посмотрев в БД данные, я сначала из-за у-умлаут грешил на слово "Zürich". Но сохранив текст в двух вариантах в файл (UTF8 и ANSI) и сравнив их, я увидел, что разница была в "Дадаи́зм" и "Дадаи?зм". Таким образом врагом WIN1251 объявляю букву "и" с ударением!

Враг назначен, т.е. найден, теперь будем решать, что с ним делать.

Вариант, подсказанный гуглом для подобного случая с WIN1252 "(replace((mi.complemento)::text, chr(128), 'C'::text))::character varying(400)" я реализовывать не стал - так на все подобные случаи replace'ов не напасешься... Я сразу взялся за кодировку. Проверил кодировку сервера PostgreSQL:
SHOW server_encoding;
Результат: UTF8
Проверил кодировку моего подключения к PostgreSQL:
var
  qEncoding: TUniQuery;
...
  qEncoding.SQL.Text :='SHOW client_encoding';
  qEncoding.Open;
  Writeln(qEncoding.Fields[0].AsString);

Результат: WIN1251
Очевидно, что они должны совпадать. Как задать кодировку клиента при подключении к серверу PostgreSQL? В разделе 23.3.3. Automatic Character Set Conversion Between Server and Client документации PostgreSQL написано:
SET client_encoding TO 'UTF8';
Значит пробуем
var
  dbPG: TUniConnection;
...
  dbPG.Open;
  dbPG.ExecSQL('SET client_encoding TO ''UTF8''');

  qEncoding.SQL.Text :='SHOW client_encoding';
  qEncoding.Open;
  Writeln(qEncoding.Fields[0].AsString);

Результат: UTF8
Значит кодировка подключения у клиента поменялась. Запускаю программу и... опять получаю ту же ошибку :-(

Проба задать свойство "client_encoding" как параметр в SpecificOptions у TUniConnection:
dbPG.SpecificOptions.Add('client_encoding=UTF8');
приводит к новой ошибке:
Project XYZ.exe raised exception class Exception with message '"client_encoding" is not a valid option name for PostgreSQL UniProvider'
Дальше, пробуя различные варианты SpecificOptions, мне удалось с помощью параметра "CharSet" поменять кодировку:
dbPG.SpecificOptions.Add('CharSet=UTF8');
qEncoding.SQL.Text :='SHOW client_encoding';

qEncoding.Open;
Writeln(qEncoding.Fields[0].AsString);

Результат: UTF8
Все отлично: программа запустилась, кодировка подключения у клиента стала UTF8 и программа успешно обработала данные! Но результат ее работы почему-то не понравился коллеге, который занимается front-end'ом. У него, видите ли, русский текст превратился в непонятный набор символов.

После вкусного обеда мне пришла свежая мысль и проблема решилась с первой попытки. Как оказалось, "ларчик просто открывался" - достаточно было перед открытием UniConnection в SpecificOptions установить параметр UseUnicode в TRUE:
dbPG.SpecificOptions.Add('UseUnicode=True');
P.S. TUniConnection и TUniQuery - это классы Devart Universal Data Access Components (UniDAC) для доступа к данным в различных СУБД.

P.P.S. Дадаи́зм - авангардистское течение в литературе, изобразительном искусстве, театре и кино. Зародилось во время Первой мировой войны в нейтральной Швейцарии, в Цюрихе... Дадаизм возник как реакция на последствия Первой мировой войны, жестокость которой, по мнению дадаистов, подчеркнула бессмысленность существования. Рационализм и логика объявлялись одними из главных виновников опустошающих войн и конфликтов. ©Wikipedia

Дадаизм против рационализма и логики, а значит против программирования и программистов. Совпадение?...

Комментариев нет: