Проблемы с FPC и Oracle

Модератор: Модераторы

Проблемы с FPC и Oracle

Сообщение mdimich » 04.12.2007 14:31:28

Имеется fpc 2.2.0, Gentoo Linux 2007.0, x86_64, Oracle 10g
Пытаюсь опробовать возможности Free Pascal для работы с oracle. И сразу же наткнулся на грабли. Используя модули sqldb и oracleconnection неполучается сделать select полей из таблицы, содержащей строки в русской кодировке. Причем, проблема имеет место быть только в юникодной локали. Если локаль перевести в другую кодировку, то все работает. Краткий анализ показал, что ошибки получаются при попытке открыть запрос и профетчить строки. ORA-01406
Я сомневаюсь, чтобы я что-то неверно в программе написал (т.к. с латиницей все работает), но навсякий случай приведу код:
Код: Выделить всё
Program testora;
Uses sqldb, oracleconnection;
Var
  OraConn : TOracleConnection;
  OraTrans : TSQLTransaction;
  OraQuery : TSQLQuery;
begin
  OraConn := TOracleConnection.Create (nil);
  with OraConn do begin
    DatabaseName := 'mybase';
    UserName := 'user';
    Password := 'pass';
    HostName := '192.168.1.1';
    Open;
  end;
  OraTrans := TSQLTransaction.Create (nil);
  OraTrans.DataBase := OraConn;

  OraQuery := TSQLQuery.Create (nil);
  OraQuery.DataBase := OraConn;
  OraQuery.Transaction := OraTrans;

  OraQuery.Sql.Clear;
  OraQuery.Sql.Add('insert into aaa (a1, a2, a3) values (2, :par1, null)');
  OraQuery.Params.ParamByName ('pas1').AsString := 'Всем привет';
  OraQuery.ExecSql;

  OraQuery.Sql.Clear;
  OraQuery.Sql.Add ('select a1, a2, a3 from aaa');
  OraQuery.Open; // <-- тут получаю ошибку
  while not OraQuery.Eof do begin
    write (OraQuery.FieldByName ('a1').AsInteger);
    write (OraQuery.FieldByName ('a2').AsString);
    write (OraQuery.FieldByName ('a3').Asdatetime);
    writeln;
    OraQuery.Next;
  end;
  OraQuery.Close;

  OraQuery.Free;
  OraTrans.Free;
  OraConn.Free;
end.


Однако, в examples я нашел еще неплохой примерчик source/packages/base/oracle/example, который таки работает с русскими строками. Но там модуль не объектный и с очень ограниченными возможностями, так что использовать его в реальной работе плохо.

Может быть кто разбирался с такой проблемой, исследовал ее? В общем, я пока не знаю, что делать и жду любых советов.
mdimich
новенький
 
Сообщения: 29
Зарегистрирован: 03.12.2007 11:24:03

Сообщение mdimich » 04.12.2007 17:57:49

Хочу рассказать о найденном мною способе решения данной проблемы:
модуль oracleconnection.pp, строки, начиная с 307:
Код: Выделить всё
procedure TOracleConnection.AddFieldDefs(cursor: TSQLCursor; FieldDefs: TFieldDefs);

var Param      : POCIParam;
    tel        : ub4;

    FieldType  : TFieldType;
    FieldName  : string;
    FieldSize  : word;

    OFieldType   : ub2;
    OFieldName   : Pchar;
    OFieldSize   : sb4;
    OFNameLength : ub4;
    NumCols      : ub4;
    FOciDefine   : POCIDefine;
    OPrecision   : sb2;
    OScale       : sb1;

begin
  Param := nil;
  with cursor as TOracleCursor do
    begin
    if OCIAttrGet(FOciStmt,OCI_HTYPE_STMT,@numcols,nil,OCI_ATTR_PARAM_COUNT,FOciError) = OCI_ERROR then
      HandleError;

    // Let op, moet gewist worden. En in een keer gealloceerd
    Setlength(FieldBuffers,numcols);

    for tel := 1 to numcols do
      begin
      if OCIParamGet(FOciStmt,OCI_HTYPE_STMT,FOciError,Param,tel) = OCI_ERROR then
        HandleError;

      if OCIAttrGet(Param,OCI_DTYPE_PARAM,@OFieldType,nil,OCI_ATTR_DATA_TYPE,FOciError) = OCI_ERROR then
        HandleError;

      if OCIAttrGet(Param,OCI_DTYPE_PARAM,@OFieldSize,nil,OCI_ATTR_DATA_SIZE,FOciError) = OCI_ERROR then
        HandleError;

      FieldSize := 0;
     
      case OFieldType of
        OCI_TYPECODE_NUMBER   : begin
                                if OCIAttrGet(Param,OCI_DTYPE_PARAM,@Oprecision,nil,OCI_ATTR_PRECISION,FOciError) = OCI_ERROR then
                                  HandleError;
                                if OCIAttrGet(Param,OCI_DTYPE_PARAM,@Oscale,nil,OCI_ATTR_SCALE,FOciError) = OCI_ERROR then
                                  HandleError;

                                if Oscale = 0 then
                                  begin
                                  FieldType := ftInteger;
                                  OFieldType := SQLT_INT;
                                  OFieldSize:= sizeof(integer);
                                  end
                                else if (oscale = -127) {and (OPrecision=0)} then
                                  begin
                                  FieldType := ftFloat;
                                  OFieldType := SQLT_FLT;
                                  OFieldSize:=sizeof(double);
                                  end
                                else if (oscale <=4) and (OPrecision<=12) then
                                  begin
                                  FieldType := ftBCD;
                                  FieldSize := sizeof(Currency);
                                  OFieldType := SQLT_VNU;
                                  OFieldSize:= 22;
                                  end
                                else FieldType := ftUnknown;
                                end;
        OCI_TYPECODE_CHAR,
        OCI_TYPECODE_VARCHAR,
        OCI_TYPECODE_VARCHAR2 : begin
              FieldType := ftString;
              OFieldSize := OFieldSize*2;  // <-- для русского юникода выделить больше памяти
              FieldSize := OFieldSize;
              inc(OFieldsize);
              OFieldType:=SQLT_STR;
            end;
        OCI_TYPECODE_DATE     : FieldType := ftDate;
        OCI_TYPECODE_TIMESTAMP,
        OCI_TYPECODE_TIMESTAMP_LTZ,
        OCI_TYPECODE_TIMESTAMP_TZ  : begin
                                     FieldType := ftDateTime;
                                     OFieldType := SQLT_ODT;
                                     end;
      else
        FieldType := ftUnknown;
      end;

      FieldBuffers[tel-1].buffer := getmem(OFieldSize);

      FOciDefine := nil;
      if OciDefineByPos(FOciStmt,FOciDefine,FOciError,tel,fieldbuffers[tel-1].buffer,OFieldSize,OFieldType,@(fieldbuffers[tel-1].ind),nil,nil,OCI_DEFAULT) = OCI_ERROR then
        HandleError;

      if OCIAttrGet(Param,OCI_DTYPE_PARAM,@OFieldName,@OFNameLength,OCI_ATTR_NAME,FOciError) <> OCI_SUCCESS then
        HandleError;

      setlength(Fieldname,OFNameLength);
      move(OFieldName^,Fieldname[1],OFNameLength);

      TFieldDef.Create(FieldDefs, FieldName, FieldType, FieldSize, False, tel);
      end;
  end;
end;

Как оказалось, при utf-8 кодировке локали, оракл возвращает размер символьного поля неверно. Или же это просто (фича такая?) длина текста строки, а размер буфера для хранения должен быть больше при многобайтных кодировках? В общем, надо бы изучить эту проблему при случае более детально. Так что пока я сделал достаточно тупо: т.к. для русского языка юникод двухбайтный, то просто уверичиваю размер буфера в 2 раза. Хотя по идее, надо анализировать локаль еще, но это дело техники, главное причина ясна. Кроме того, надо будет проверить, чтобы такое мое шаманство не вызвало лишних утечек нигде....

Дя и забыл сказать, что все эксперименты у меня были при кодировке базы:AL16UTF16, и для клиента NLS_LANG=AMERICAN_CIS.AL32UTF8 и LANG=ru_RU.UTF-8 Как проявится это при внутренней кодировке базы, отличной от моей, я не проверял.
mdimich
новенький
 
Сообщения: 29
Зарегистрирован: 03.12.2007 11:24:03

Сообщение Attid » 04.12.2007 18:01:02

mdimich
я оракл 100 лет в глаза не видел, но может у него есть возможность указать с какой кодировкой подключатся ?
Аватара пользователя
Attid
долгожитель
 
Сообщения: 2585
Зарегистрирован: 27.10.2006 17:29:15
Откуда: 44°32′23.63″N 41°2′25.2″E

Сообщение mdimich » 04.12.2007 18:13:17

Attid, мысль кстати хорошая. Если я не ошибаюсь, то в закрытой библиотеке ODAC от CoreLab такое есть - или автодетект или ручками указать. Но в классах этих модулей, с которыми копался, - на это не нашел даже намека. Вероятно это делается каким-то специальным вызовом OCI. Надо будет почитать макулатуру на эту тему, поспрашивать у ораклоидов. Вероятно кошернее просто дописать нужный метод.

Хотя, это как еще подумать. Кодировку то оракл верную возвращает. Это уже мы неверно расчитываем размер поля. Скорее надо не тупо, как я длину строки (полученный размер поля) умножать на 2, а умножать на кол-во байт в символе кодировки. Например для CP1251, KOI8R и иже с ними это будет 1, для кириллицы в utf-8 это 2, а там для китайских всяких в utf-8 уже 4.

Кстати, а как с разработчиками этого модуля связаться не подскажите? В readme.txt от sqldb я нашел такое: 'SQLDB readme file, 20 Aug 2005, Joost van der Sluis'. Joost van der Sluis это автор модуля или это что-то другое (не смыслю в фамилиях ничего).
mdimich
новенький
 
Сообщения: 29
Зарегистрирован: 03.12.2007 11:24:03


Вернуться в Базы данных

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 4

Рейтинг@Mail.ru