Добро пожаловать в форум, Guest  >>   Войти | Регистрация | Поиск | Правила | В избранное | Подписаться
Все форумы / Delphi Новый топик    Ответить
 Сохранение данных в БД c автоматическим приведением к UTF-8  [new]
amsdev
Member

Откуда:
Сообщений: 51
Всем привет !

Встала вот такая задача:

Есть средних размеров проект, который до недавнего времени был на Delphi 2007 (точнее, на C++Builder'е, но это не важно). В качестве БД используется DBISAM от ElevateSoftware, который всем устраивает кроме того, что не поддерживает юникод т.е. данные хранятся в ANSI с использованием системной кодировки.

Сейчас идет перевод проекта на 10.3.3 Rio и юникод и возникла проблема с хранением данных
в БД: поскольку весь интерфейс стал поддерживать юникод - пользователь может ввести любые строки в т.ч. содержащие юникодные эмоджи и т.д. Если сохранять их базу как есть - все, что выходит за пределы ANSI будет вопросиками. Чтобы этого избежать - строковые данные необходимо приводить из юникода в UTF-8 перед сохранением в базу и из UTF-8 в юникод - при чтении, отображении в db-aware контролах, при поиске и т.д.

В этой связи вопрос: может быть у кого-то была подобная проблема и элегантный способ ее решения чтобы не писать UTF8ToString/StringToUTF8 в тысяче местах ?

Или это таит в себе геморой внутри гемороя обернутый в геморой и все-таки стоит перейти на другой движек БД с поддержкой юникода из коробки ? Хотя с переходом проблем не меньше если не больше...

Большое спасибо за советы ! )

Сообщение было отредактировано: 1 июн 20, 13:33
1 июн 20, 13:34    [22143636]     Ответить | Цитировать Сообщить модератору
 Re: Сохранение данных в БД c автоматическим приведением к UTF-8  [new]
kdv
Member

Откуда: iBase.ru
Сообщений: 28883
amsdev,

вопрос в том, что там у этого DBISAM с кодировками коннекта. Если их вообще нет, тогда да, придется конвертировать. Или написать своего наследника (что кмк проще), который будет это делать для всех данных, переправляемых в базу и обратно.
А поскольку юникодная Дельфи 2009 существует уже 10 лет, вероятно, кто-то это уже сделал.

Например, у IBX с Firebird если чарсет коннекта win1251, то в базу ничего юникодного не залезет по определению.
1 июн 20, 14:22    [22143691]     Ответить | Цитировать Сообщить модератору
 Re: Сохранение данных в БД c автоматическим приведением к UTF-8  [new]
softwarer
Member

Откуда: 127.0.0.1
Сообщений: 62435
Блог
Я бы рассмотрел по порядку приоритетов следующие подходы:

1. Если архитектура приложения правильная, и в используются собственные компоненты-обёртки, то либо на уровне "своей обёртки над полем ввода" блокировать ввод неансишных символов, либо на уровне "своей обёртки над датасетом" убирать их перед сохранением, сообщать об ошибке и недопустимом значении итп.

2. Если архитектура приложения частично правильная, то на уровне базового класса формы развешать по полям ввода какие-нибудь OnChange, которые блокируют ввод неансишных символов.

3. Рассмотреть сложность и трудоёмкость рефакторинга на правильную архитектуру (с обёртками), потом см. пункт 1

4. Забить. В небольших проектах со стабильными пользователями такие неаккуратности несолидны, но не существенны. Ну вопросики и вопросики - у нас что, нет более актуальных задач?

5. Подумать над переходом на какую-нибудь более вменяемую СУБД, в том числе с поддержкой юникода. Когда-нибудь всё равно придётся.

6. Отпинать вендора этого чуда на предмет поддержки юникода.
1 июн 20, 14:23    [22143693]     Ответить | Цитировать Сообщить модератору
 Re: Сохранение данных в БД c автоматическим приведением к UTF-8  [new]
amsdev
Member

Откуда:
Сообщений: 51
автор
вопрос в том, что там у этого DBISAM с кодировками коннекта.


Нет, данные приводятся к системной кодировке и сохраняются как ANSI.

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


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

В хэлпе нарыл TStringField.Transliterate и TDataSet.Translate, которые делают почти то, что мне нужно, но проблема в том, что у TDataSet функция объявлена:

function Translate(Src, Dest: PAnsiChar; ToOem: Boolean): Integer;

На вход приходит текст в UTF8, но как его отдать в Dest как юникод ? Видимо никак (((

Вероятно придется делать своего наследника TDataSet...
1 июн 20, 15:14    [22143781]     Ответить | Цитировать Сообщить модератору
 Re: Сохранение данных в БД c автоматическим приведением к UTF-8  [new]
X11
Member

Откуда: Kharkiv, Ukraine
Сообщений: 14193
Когда я конвертировал базу Firebird, я делал это в два подхода через SQL-скрипты.
1 июн 20, 16:26    [22143853]     Ответить | Цитировать Сообщить модератору
 Re: Сохранение данных в БД c автоматическим приведением к UTF-8  [new]
amsdev
Member

Откуда:
Сообщений: 51
автор
Когда я конвертировал базу Firebird, я делал это в два подхода через SQL-скрипты.


Это понятно, что нужно будет имеющиеся данные конвертировать в UTF-8.

Вопрос в том, как избежать правки

Table.FieldByName('SomeField').AsString:=StringToUTF8(SomeValue);
и
SomeValue:=UTF8ToString(Table.FieldByName('SomeField').AsString);

Как избавиться от StringToUTF8 и UTF8ToString чтобы не править код в тысяче мест ? Для db-aware контролов можно использовать эвент TStringField.OnGetText и все будет показано нормально, но во всех остальных местах править это караул.

Сообщение было отредактировано: 1 июн 20, 17:26
1 июн 20, 17:26    [22143878]     Ответить | Цитировать Сообщить модератору
 Re: Сохранение данных в БД c автоматическим приведением к UTF-8  [new]
softwarer
Member

Откуда: 127.0.0.1
Сообщений: 62435
Блог
amsdev
Вопрос не в том, чтобы запретить ввод тех или иных символов, наоборот, нужно чтобы пользователь мог ввести любую строку на любом языке.

И сохранять их в неюникодной базе, для чего конвертировать в какую-нибудь хрень? Нет, тогда бы я однозначно поставил бы вопрос о миграции на какую-нибудь вменяемую СУБД. Не потому, что иначе не сделать, а потому, что не стал бы плодить ещё большую фигню.
1 июн 20, 17:33    [22143885]     Ответить | Цитировать Сообщить модератору
 Re: Сохранение данных в БД c автоматическим приведением к UTF-8  [new]
shalamyansky
Member

Откуда:
Сообщений: 156
Тут важно понимать, используются ли строковые поля в базе только для хранения строк, или же еще и для поиска и других серверных операций с символьными последовательностями.

Если только для хранения, то то проблемы почти и нет, строки можно рассматривать как байтовые массивы, и засовывать в них вообще все, что угодно, не глядя на кодировки. Единственная, правда, существенная, проблема - нулевой символ 0x00. Данные могут обрезаться по нему. А могут и не обрезаться, зависит от реализации, надо проверять экспериментально.

Если же для строк требуется сравнение на стороне сервера (поиск, индекс), то оно возможно только для для той кодировки, которую сервер поддерживает. ANSI, значит, ANSI (win1251?), и никакого UTF-8. Зачем приводить к UTF-8, если сервер все равно не умеет с ней работать?
1 июн 20, 18:02    [22143903]     Ответить | Цитировать Сообщить модератору
 Re: Сохранение данных в БД c автоматическим приведением к UTF-8  [new]
amsdev
Member

Откуда:
Сообщений: 51
Задачу решил вот таким образом, все работает быстро, ровно и без глюков ! Может кому окажется полезным. Мне были нужны только прозрачные запись и отображение. Для поиска (locate) или SQL запросов - можно аналогичным образом переопределить нужные методы и вставить туда кодирование в UTF8, мне это было не нужно поэтому пока все работает в таком варианте.

unit dbisamtbutf8;

interface

uses System.Classes, System.Variants, Data.DB, dbisamtb;

type

TUTF8StringField = class(TStringField) 
private
  FAutoUtf8Conversion:     Boolean;
protected
  function                 GetAsString: string; override;
  function                 GetAsVariant: Variant; override;
  procedure                SetAsString(const Value: string); override;
public
  constructor              Create(AOwner: TComponent); override;
published
  property                 AutoUtf8Conversion: Boolean read FAutoUtf8Conversion write FAutoUtf8Conversion;
end;

TUTF8DBISAMTable = class(TDBISAMTable) 
private
  FAutoUTF8Conversion:     Boolean;
  procedure                SetAutoUTF8Conversion(const NewValue:Boolean);
public
  constructor              Create(AOwner: TComponent); override;
  procedure                Open; overload;
  function                 GetFieldClass(FieldType: TFieldType): TFieldClass; override;
published
  property                 AutoUTF8Conversion:Boolean read FAutoUTF8Conversion write SetAutoUTF8Conversion;
end;

implementation

constructor TUTF8StringField.Create(AOwner: TComponent);
begin
  FAutoUtf8Conversion:=false;
  inherited Create(AOwner);
end;

function TUTF8StringField.GetAsString: string;
begin
  if FAutoUtf8Conversion then
    Result:=UTF8ToString(GetAsAnsiString)
  else
    Result:=inherited GetAsString;
end;

function TUTF8StringField.GetAsVariant: Variant;
begin
  if FAutoUtf8Conversion then
    Result:=UTF8ToString(AnsiString(inherited GetAsVariant))
  else
    Result:=inherited GetAsVariant;
end;

procedure TUTF8StringField.SetAsString(const Value: string);
begin
  if FAutoUtf8Conversion then
    inherited SetAsAnsiString(UTF8Encode(Value))
  else
    Inherited SetAsString(Value);
end;

{ TUTF8DBISAMTable }
constructor TUTF8DBISAMTable.Create(AOwner: TComponent);
begin
  FAutoUTF8Conversion:=false;
  inherited Create(AOwner);
end;

function TUTF8DBISAMTable.GetFieldClass(FieldType: TFieldType): TFieldClass;
begin
  if FieldType=ftString then
    Result:=TUTF8StringField
  else
    Result:=inherited GetFieldClass(FieldType);
end;

procedure TUTF8DBISAMTable.SetAutoUTF8Conversion(const NewValue: Boolean);
var
  i:Integer;
begin
  for i := 0 to Fields.Count-1 do
    begin
    if Fields.Fields[i] is TUTF8StringField then
      TUTF8StringField(Fields.Fields[i]).AutoUtf8Conversion:=NewValue;
    end;
  FAutoUTF8Conversion:=NewValue;
end;

procedure TUTF8DBISAMTable.Open;
begin
  inherited Open;
  SetAutoUTF8Conversion(FAutoUTF8Conversion);
end;

end.
18 июн 20, 01:22    [22152747]     Ответить | Цитировать Сообщить модератору
 Re: Сохранение данных в БД c автоматическим приведением к UTF-8  [new]
b0rk
Member

Откуда: Харьков
Сообщений: 599
если база не поддерживает UTF8, то при таком подходе могут неправильно строковые функции внутри SQL запросов. например SUBSTRING('текст', 3, 4) будет брать часть строки не с стретьего символа, а с третьего байта, в результате чего получите половинку буквы Е и букву К.
18 июн 20, 10:54    [22152828]     Ответить | Цитировать Сообщить модератору
 Re: Сохранение данных в БД c автоматическим приведением к UTF-8  [new]
shalamyansky
Member

Откуда:
Сообщений: 156
amsdev
Для поиска (locate) или SQL запросов - можно аналогичным образом переопределить нужные методы и вставить туда кодирование в UTF8

Поиск на клиенте работать будет, но любой запрос вида
select
    *
  from
    persons
  where
    name='Вася'

или
select
    *
  from
    persons
  order by
    name

вас огорчит.
19 июн 20, 19:33    [22153937]     Ответить | Цитировать Сообщить модератору
Все форумы / Delphi Ответить