Страницы

13 марта 2012 г.

ZeosLib. Впечатления от использования в Lazarus

Пишу информационную систему на Free Pascal и PostgreSQL.
Изначально использовал стандартные компоненты (TPQConnection, TSQLQuery и т.д.).
Для того чтобы было проще рулить механизмами взаимодействия с БД - написал диспетчер компонентов.

Суть диспетчера - приведение любого компонента работающего с БД к единому интерфейсу. Таким образом, если мне понадобилось заменить компоненты доступа - надо для этих компонентов написать несколько наследников диспетчера. Для маленьких проектов это не рационально, а вот для больших... Очень даже. Такой подход так же подразумевает создание всех объектов доступа динамически, Но это уже другая история... Которую я опишу позже...

Итак, Zeos*.
Стандартные компоненты хорошо справлялись с поставленными тривиальными задачами, но наступил момент когда нам понадобилась обратная связь с сервером БД. Обратная связь средствами исключений не годится по понятным, думаю, причинам.
Конкретно мне было необходимо автоматически обновлять датасет на клиенте при наступлении определенных событий в базе данных.

Помимо Zeos, такими способностями обладает PostgreDAС... Но он платный - неприемлемо.

Для того чтобы использовать Zeos в совокупности с PostgreSQL,  придется немного переделать исходники самого зеоса. Эта малая плата за бесплатность функционала который он предоставляет :).
Проблема в том что из-за синтаксических особенностей диалекта SQL который использует PostgreSQL, Zeos не всегда понимает, что выполняемый запрос синтаксически правильный. Придется подправить парсер следующим образом:
В модуле ZSQLStrings нужно заменить тело метода TZSQLStrings.RebuildAll:

procedure TZSQLStrings.RebuildAll;
var
  Tokens: TStrings;
  TokenValue: string;
  TokenType: TZTokenType;
  TokenIndex: Integer;
  ParamIndex: Integer;
  ParamIndices: TIntegerDynArray;
  ParamIndexCount: Integer;
  ParamName, SQL: string;
  Tokenizer: IZTokenizer;

  procedure NextToken;
  begin
    TokenType := TZTokenType({$IFDEF FPC}Pointer({$ENDIF}
      Tokens.Objects[TokenIndex]{$IFDEF FPC}){$ENDIF});
    TokenValue := Tokens[TokenIndex];
    Inc(TokenIndex);
  end;

begin
  FParams.Clear;
  FStatements.Clear;
  SQL := '';
  ParamIndexCount := 0;
  SetLength(ParamIndices, ParamIndexCount);
 
  { Optimization for empty query. }
  If Length(Trim(Text)) = 0 then
    Exit;

  { Optimization for single query without parameters. }
  if (not FParamCheck or (Pos(FParamChar, Text) = 0))
    and (not FMultiStatements or (Pos(';', Text) = 0)) then
  begin
    FStatements.Add(TZSQLStatement.Create(Text, ParamIndices, FParams));
    Exit;
  end;

  Tokenizer:=GetTokenizer;
  Tokens := Tokenizer.TokenizeBufferToList(Text,
    [toSkipComments, toUnifyWhitespaces]);
  try
    TokenIndex := 0;
    repeat
      NextToken;
      { Processes parameters. }
      if ParamCheck and (TokenValue = FParamChar) then
      begin
        NextToken;
        if (TokenType <> ttEOF) and (TokenValue <> FParamChar) and (TokenValue <> '=') then
        begin
          { Check for correct parameter type. }
          if not (TokenType in [ttWord, ttQuoted]) then
            raise EZDatabaseError.Create(SIncorrectToken);

          SQL := SQL + '?';

          ParamName := TokenValue;
          if (ParamName <> '') and (ParamName[1] in [#39, '`', '"', '[']) then
          begin
            ParamName := Tokenizer.GetQuoteState.
              DecodeString(ParamName, ParamName[1]);
          end;

          ParamIndex := FindParam(ParamName);
          if ParamIndex < 0 then
            ParamIndex := FParams.Add(ParamName);

          Inc(ParamIndexCount);
          SetLength(ParamIndices, ParamIndexCount);
          ParamIndices[ParamIndexCount - 1] := ParamIndex;

          Continue;
        end
        else
          if (TokenType <> ttEOF) and ((TokenValue = ':') or (TokenValue = '=')) then
            SQL := SQL + ':';
      end;

      { Adds a DML statement. }
      if (TokenType = ttEOF) or (FMultiStatements and (TokenValue = ';')) then
      begin
        SQL := Trim(SQL);
        if SQL <> '' then
          FStatements.Add(TZSQLStatement.Create(SQL, ParamIndices, FParams));

        SQL := '';
        ParamIndexCount := 0;
        SetLength(ParamIndices, ParamIndexCount);
      end
      { Adds a default token. }
      else
        SQL := SQL + TokenValue;
    until TokenType = ttEOF;
  finally
    Tokens.Free;
  end;
end;

Исходник (c) Oldwayder.

После этих манипуляций Zeos будет воспринимать все запросы корректно.

Далее столкнулся с проблемой длинны поля типа Float.
По умолчанию длинна равно 10 символам, включая знак числа и разделитель целой и дробной части. Т.е. числа с десятичной точностью величиной более миллиона будут отображаться некорректно. Это проблема пока не побеждена, т.к. запросы все динамические, а при каждом открытии осуществлять поиск вещественного числа и изменять количество символов отображения - это не выход...


Если кто знает как решить вопрос - буду благодарен за помощь. Ибо как раз сейчас с этим борюсь...



*в данной статье рассматривается Zeos 7.0

2 комментария: