
//////////////////////////////////////////////////
//  InterBase Data Access Components
//  Copyright © 1996-2014 Devart. All right reserved.
//////////////////////////////////////////////////

{$IFNDEF CLR}
{$I IbDac.inc}
unit IBCServicesUni;
{$ENDIF}

interface

uses
{$IFDEF VER6P}
  Variants,
{$ENDIF}
{$IFNDEF CLR}
  CLRClasses,
{$ELSE}
  System.Text,
{$ENDIF}
  SysUtils, Classes, DB, CRTypes, {$IFNDEF FPC}MemDS{$ELSE}MemDataSet{$ENDIF},
  MemData, DBAccess, CRAccess, DADump, DASQLGenerator,
  DASQLMonitor,
{$IFNDEF UNIDACPRO}
  IBCCall, IBCClasses, IBCSQLGenerator;
{$ELSE}
  IBCCallUni, IBCClassesUni, IBCSQLGeneratorUni;
{$ENDIF}

const
  prGeneratorMode = 101;  // TGeneratorMode
  prGeneratorStep = 102;  // integer

type
  TCustomIBCDataSetService = class;

  TCustomIBCDataSetUpdater = class(TDADataSetUpdater)
  protected
    FDataSetService: TCustomIBCDataSetService;

    procedure GetGeneratorVal;
    function Sequenced: boolean;
    function SavepointAllowed: boolean; override;
    function CanRefreshByLock: boolean; override;

    function FieldByParamName(var ParamName: string; out Old: boolean; out AFieldNo: integer; out Master: boolean): TField; override;
    procedure CheckUpdateQuery(const StatementType: TStatementType); override;
    procedure SetUpdateQueryOptions(const StatementType: TStatementType); override;

    function BatchUpdate: boolean; override;
    function UseParamType(Param: TDAParam): boolean; override; //This function indicates ParamType using in PerformSQL
    function NeedReturnParams: boolean; override;
    function IsNeedEditPreconnect: boolean; override;
    function IsNeedInsertPreconnect: boolean; override;

    procedure PrepareAppend; override;

    function PerformAppend: boolean; override;

  public
    constructor Create(AOwner: TDataSetService); override;

    function GetDefaultExpressionValue(DefExpr: string; out Value: variant): boolean; override;
  end;

  TCustomIBCDataSetService = class(TDADataSetService)
  protected
    FUpdater: TCustomIBCDataSetUpdater;
    FSQLGenerator: TCustomIBCSQLGenerator;

    procedure CreateDataSetUpdater; override;
    procedure SetDataSetUpdater(Value: TDataSetUpdater); override;
    procedure CreateSQLGenerator; override;
    procedure SetSQLGenerator(Value: TDASQLGenerator); override;

    function IsDMLRefresh: boolean;
    function IsAutoCommit: boolean;

    procedure SetNumberRange(FieldDef: TFieldDef); override;
    function DetectHiddenFields: TFieldArray; override;
    procedure FillFieldsDefaultValues; override;
    function DetectCanModify: boolean; override;
    function GetRecCount: integer; override;

  public
    function SetProp(Prop: integer; const Value: variant): boolean; override;

    function GetDBKeyList(const TableName, IndexName: string): string; override;
  end;

  TCustomIBCFieldTypeMap = class(TDAFieldTypeMap)
  public
    class function GetFieldType(DataType: Word): TFieldType; override;
    class function GetDataType(FieldType: TFieldType): integer; override;
  end;

  // must use inherited class to 'is' in ChechServerEnumerator works correctly
  TIBCServerEnumerator = class (TCRServerEnumerator)
  end;

  TCustomIBCDumpProcessor = class(TDADumpProcessor)
  protected
    function GetFieldValueForDump(Field: TField): string; override;
  end;

implementation

uses
  Math, DAConsts, CRParser, CRFunctions;

{ TCustomIBCDataSetUpdater }

constructor TCustomIBCDataSetUpdater.Create(AOwner: TDataSetService);
begin
  inherited;

  FDataSetService := TCustomIBCDataSetService(AOwner);
end;

procedure TCustomIBCDataSetUpdater.GetGeneratorVal;
var
  Value: variant;
begin
  if not ((FDataSetService.FSQLGenerator.GeneratorMode = _gmPost) and Sequenced) and (FDataSetService.KeyGeneratorField <> nil) then begin
    Value := SelectDbValue('Get generator value',
      Format('SELECT GEN_ID(%s, %d) FROM RDB$DATABASE',
      [FDataSetService.FSQLGenerator.NormalizeStrValue(FDataSetService.FSQLGenerator.KeySequence), FDataSetService.FSQLGenerator.GeneratorStep]));
    FDataSetService.SetKeyGeneratorValue(Value);
  end;
end;

function TCustomIBCDataSetUpdater.Sequenced: boolean;
var
  Connection: TGDSConnection;
begin
  Connection := TGDSConnection(TDBAccessUtils.GetIConnection(UsedConnection));
  Result := (FDataSetService.KeyGeneratorField <> nil) and
    Connection.IsFBServer and (Connection.GetMajorServerVersion >= 2) and
    (FDataSet.SQLInsert.Text = '') and // Pass through case when Gen value applied using GetInsertSQL
    ((GetUpdateObject = nil) or ((GetUpdateObject.InsertObject = nil) and (GetUpdateObject.InsertSQL.Text = '')));
end;


function TCustomIBCDataSetUpdater.NeedReturnParams: boolean;
begin
  Result := inherited NeedReturnParams or
    ((FDataSet.State = dsInsert) and (FDataSetService.IsDMLRefresh or (FDataSetService.IdentityField <> nil))); // Db_Key or Returning in insert statements
end;

function TCustomIBCDataSetUpdater.FieldByParamName(var ParamName: string; out Old: boolean; out AFieldNo: integer; out Master: boolean): TField;
begin
  //Returning support
  if (CompareText(Copy(ParamName, 1, 4), 'RET_') = 0) and (FDataSet.FindField(ParamName) = nil) then
    ParamName := Copy(ParamName, 5, Length(ParamName) - 4);

  Result := inherited FieldByParamName(ParamName, Old, AFieldNo, Master);
end;

procedure TCustomIBCDataSetUpdater.CheckUpdateQuery(const StatementType: TStatementType);
var
  UpdateDataSet: TCustomDADataSet;
begin
  inherited;

  UpdateDataSet := FUpdateQuery as TCustomDADataSet;
  if StatementType = stLock then
    TDBAccessUtils.SetAutoCommit(UpdateDataSet, False)
  else
    TDBAccessUtils.SetAutoCommit(UpdateDataSet, FDataSetService.IsAutoCommit);
  TDBAccessUtils.SetFetchAll(UpdateDataSet, True);
  UpdateDataSet.Options.QueryRecCount := False;
  UpdateDataSet.Options.QuoteNames := False;
  TDBAccessUtils.GetICommand(UpdateDataSet).SetProp(prQueryRowsAffected, FDataSet.Options.StrictUpdate); //for right RowsAffected behaviour
end;

procedure TCustomIBCDataSetUpdater.SetUpdateQueryOptions(const StatementType: TStatementType);
var
  DestRecordSet, SourceRecordSet: TCRRecordSet;
  DestCommand, SourceCommand: TCRCommand;

  procedure CopyPropR(Prop: integer);
  var
    v: variant;
  begin
    SourceRecordSet.GetProp(Prop, v);
    DestRecordSet.SetProp(Prop, v);
  end;

  procedure CopyPropC(Prop: integer);
  var
    v: variant;
  begin
    SourceCommand.GetProp(Prop, v);
    DestCommand.SetProp(Prop, v);
  end;

begin
  CheckIRecordSet; // can be inactive
  SourceRecordSet := GetIRecordSet;
  DestRecordSet := TDBAccessUtils.GetIRecordSet(FUpdateQuery as TCustomDADataSet);
  SourceCommand := GetICommand;
  DestCommand := TDBAccessUtils.GetICommand(FUpdateQuery as TCustomDADataSet);

  CopyPropR(prFieldsAsString);
  CopyPropC(prCacheBlobs);
  CopyPropC(prStreamedBlobs);
  CopyPropR(prComplexArrayFields);
  CopyPropC(prCacheArrays);
  CopyPropR(prDeferredArrayRead);
  CopyPropR(prBooleanDomainFields);
  CopyPropC(prUseDescribeParams);
end;

function TCustomIBCDataSetUpdater.IsNeedEditPreconnect: boolean;
begin
  Result := not FDataSet.CachedUpdates and (GetLockMode = lmPessimistic);
end;

function TCustomIBCDataSetUpdater.IsNeedInsertPreconnect: boolean;
begin
  Result := FDataSetService.FSQLGenerator.GeneratorMode = _gmInsert;
end;

function TCustomIBCDataSetUpdater.BatchUpdate: boolean;
begin
  Result := False;
end;

function TCustomIBCDataSetUpdater.UseParamType(Param: TDAParam): boolean;  //This function indicates ParamType using in PerformSQL
begin
  Result := True;
end;

function TCustomIBCDataSetUpdater.GetDefaultExpressionValue(DefExpr: string; out Value: variant): boolean;
begin
  Result := True;
  DefExpr := Trim(DefExpr);
  if (DefExpr = 'NOW') or
    (Pos('CURRENT_TIME', DefExpr) = 1) or (Pos('CURRENT_TIMESTAMP', DefExpr) = 1) then
    Value := Now
  else
  if (DefExpr = 'TODAY') or (DefExpr = 'CURRENT_DATE') then
    Value := Date
  else
  if (DefExpr = 'TOMORROW') then
    Value := Date + 1
  else
  if (DefExpr = 'YESTERDAY') then
    Value := Date - 1
  else
  if (DefExpr = IBDAC_EMPTY_STRING) then
    Value := ''
  else
    Result := inherited GetDefaultExpressionValue(DefExpr, Value);
end;

procedure TCustomIBCDataSetUpdater.PrepareAppend;
begin
  if (FDataSetService.FSQLGenerator.GeneratorMode = _gmInsert) and (FDataSetService.KeyGeneratorField <> nil) then
    GetGeneratorVal;
end;

function TCustomIBCDataSetUpdater.PerformAppend: boolean;
var
  OldReturnParams, OldStrictUpdate: boolean;
begin
  OldReturnParams := FDataSet.Options.ReturnParams;
  OldStrictUpdate := FDataSet.Options.StrictUpdate;
  if Sequenced then begin
    FDataSet.Options.ReturnParams := True; //To retrieve Generator value
    FDataSet.Options.StrictUpdate := False; //This statement desribed as storedproc so RecordsAffected check will fails
  end;
  try
    if (FDataSetService.FSQLGenerator.GeneratorMode = _gmPost) and not Sequenced then
      GetGeneratorVal;

    Result := inherited PerformAppend;
  finally
    if Sequenced then begin
      FDataSet.Options.ReturnParams := OldReturnParams;
      FDataSet.Options.StrictUpdate := OldStrictUpdate;
    end;
  end;
end;

function TCustomIBCDataSetUpdater.SavepointAllowed: boolean;
var
  Connection: TGDSConnection;
begin
  //There is some wrong Unlock behaviour
  //The waiting transaction would't be notified
  //after Rollback to Savepoint (see Fb 1.5 release notes)

  Connection := TGDSConnection(TDBAccessUtils.GetIConnection(UsedConnection));
  //FB 1.5 and IB 7.1
  if Connection.IsFBServer then
    Result := ((Connection.GetMajorServerVersion > 1) or
    ((Connection.GetMajorServerVersion = 1) and (Connection.GetMinorServerVersion >= 5)))
  else
    Result := ((Connection.GetMajorServerVersion > 7) or
    ((Connection.GetMajorServerVersion = 7) and (Connection.GetMinorServerVersion >= 1)));
end;

function TCustomIBCDataSetUpdater.CanRefreshByLock: boolean;
begin
  Result := False;
end;

{ TCustomIBCDataSetService }

function TCustomIBCDataSetService.SetProp(Prop: integer; const Value: variant): boolean;
begin
  Result := True;
  case Prop of
    prGeneratorMode:
      FSQLGenerator.GeneratorMode := _TGeneratorMode(Value);
    prGeneratorStep:
      FSQLGenerator.GeneratorStep := Value;
    prAutoCommit:
      SetAutoCommit(Value);
  else
    Result := inherited SetProp(Prop, Value);
  end;
end;

procedure TCustomIBCDataSetService.CreateDataSetUpdater;
begin
  SetDataSetUpdater(TCustomIBCDataSetUpdater.Create(Self));
end;

procedure TCustomIBCDataSetService.SetDataSetUpdater(Value: TDataSetUpdater);
begin
  inherited;

  FUpdater := TCustomIBCDataSetUpdater(Value);
end;

procedure TCustomIBCDataSetService.CreateSQLGenerator;
begin
  SetSQLGenerator(TCustomIBCSQLGenerator.Create(TDASQLGeneratorService));
end;

procedure TCustomIBCDataSetService.SetSQLGenerator(Value: TDASQLGenerator);
begin
  inherited;

  FSQLGenerator := TCustomIBCSQLGenerator(Value);
end;

function TCustomIBCDataSetService.IsDMLRefresh: boolean;
begin
  Result := inherited IsDMLRefresh;
end;

function TCustomIBCDataSetService.IsAutoCommit: boolean;
begin
  Result := inherited IsAutoCommit;
end;

function TCustomIBCDataSetService.DetectHiddenFields: TFieldArray;
var
  i: integer;
  FieldDesc: TCRFieldDesc;
  RecordSet: TGDSRecordSet;
  Field: TField;
begin
  Result := nil;
  RecordSet := TGDSRecordSet(GetIRecordSet);
  for i := 0 to RecordSet.Fields.Count - 1 do begin
    FieldDesc := TCRFieldDesc(RecordSet.Fields[i]);
    if FieldDesc.SubDataType = dtDbKey then begin
      Field := FDataSet.GetField(FieldDesc);
      if Field <> nil then begin
        SetLength(Result, Length(Result) + 1);
        Result[Length(Result) - 1] := Field;
      end;
    end;
  end;
end;

procedure TCustomIBCDataSetService.SetNumberRange(FieldDef: TFieldDef);
var
  Field: TField;
  FieldDesc: TFieldDesc;
  Precision: integer;
{$IFDEF VER6P}
  e: extended;
{$ENDIF}
begin
  Field := FDataSet.FindField(FieldDef.Name);
  if (Field <> nil) and (Field is TNumericField) then begin
    FieldDesc := FDataSet.GetFieldDesc(Field);
    case Field.DataType of
      ftInteger: begin
        TIntegerField(Field).MinValue := Low(Integer);
        TIntegerField(Field).MaxValue := High(Integer);
      end;
      ftSmallInt: begin
        TSmallIntField(Field).MinValue := Low(SmallInt);
        TSmallIntField(Field).MaxValue := High(SmallInt);
      end;
      ftLargeInt: begin
        TLargeIntField(Field).MinValue := Low(Int64);
        TLargeIntField(Field).MaxValue := High(Int64);
      end;
      ftBCD: begin
        Precision := FieldDesc.Length - FieldDesc.Scale;
        if Precision > 14 then
          Precision := 14; // max currency precision
        TBCDField(Field).MaxValue := IntPower(10, Precision) -
          IntPower(10, - Integer(FieldDesc.Scale));
        TBCDField(Field).MinValue := - TBCDField(Field).MaxValue;
      end;
    {$IFDEF VER6P}
    {$IFNDEF FPC}
      ftFmtBCD: begin
        e :=
          IntPower(10, FieldDesc.Length - FieldDesc.Scale) -
          IntPower(10, - Integer(FieldDesc.Scale));
        TFMTBCDField(Field).MaxValue := FloatToStr(e);
        TFMTBCDField(Field).MinValue := FloatToStr(- e);
      end;
    {$ENDIF}
    {$ENDIF}
      ftFloat: begin
        TFloatField(Field).MaxValue := 1.7E308;
        TFloatField(Field).MinValue := -1.7E308;
      end
    end;
  end;
end;

procedure TCustomIBCDataSetService.FillFieldsDefaultValues;

  procedure PrepareDefault(const Value: string; Field: TField);
  var
    OldExpr,
    DefExpr: string;
    Val: variant;
  begin
    DefExpr := Trim(Copy(Trim(Value), 8, MaxInt));
    if DefaultExpressionOldBehavior then begin
      if (DefExpr[1] = '''') and (DefExpr[Length(DefExpr)] = '''') then
        DefExpr := Copy(DefExpr, 2, Length(DefExpr) - 2);
      if (Field is TNumericField) and ({$IFDEF VER16P}FormatSettings.{$ENDIF}DecimalSeparator <> '.') and
         (Pos('.', DefExpr) > 0) then
         DefExpr[Pos('.', DefExpr)] := char({$IFDEF VER16P}FormatSettings.{$ENDIF}DecimalSeparator{$IFDEF CLR}[1]{$ENDIF});
      if DefExpr = '' then
        DefExpr := IBDAC_EMPTY_STRING; //To allow empty string in default expresion
    end
    else if (Field is TDateField)
         or (Field is TTimeField)
         or (Field is TDateTimeField)
         {$IFNDEF FPC}{$IFDEF VER6P}or (Field is TSQLTimeStampField){$ENDIF}{$ENDIF} then
      if (DefExpr[1] = '''') and (DefExpr[Length(DefExpr)] = '''') then begin
        OldExpr := DefExpr;
        DefExpr := Copy(DefExpr, 2, Length(DefExpr) - 2);
        if not FUpdater.GetDefaultExpressionValue(DefExpr, Val) then
          DefExpr := OldExpr;
    end;
    Field.DefaultExpression := DefExpr;
  end;

  function GetFieldValue(Field: TField): string;
  begin
  {$IFDEF VER12P}
    if Field is TWideMemoField then
      Result := Field.AsString
    else
      Result := {$IFNDEF NEXTGEN}string(Field.AsAnsiString){$ELSE}Field.AsString{$ENDIF};
  {$ELSE}
    Result := VarToStr(Field.Value);
  {$ENDIF}
  end;

var
  Field: TField;
  FieldDesc: TIBCFieldDesc;
  FieldName: string;
  MonitorClass: TDASQLMonitorClass;
  MessageID: cardinal;
  UpdateQuery: TCustomDADataSet;
begin
  if GetIRecordSet.UpdatingTableInfo = nil then
    Exit;

  FUpdater.CheckUpdateQuery(stCustom);
  UpdateQuery := TCustomDADataSet(FUpdater.UpdateQuery);
  UpdateQuery.SQL.Text := 'SELECT Rel.RDB$FIELD_NAME, Fld.RDB$COMPUTED_BLR, Rel.RDB$DEFAULT_SOURCE, Fld.RDB$DEFAULT_SOURCE '+
    'FROM RDB$RELATION_FIELDS Rel JOIN RDB$FIELDS Fld ON (Rel.RDB$FIELD_SOURCE = Fld.RDB$FIELD_NAME) '+
    'WHERE (Rel.RDB$RELATION_NAME = ''' + GetIRecordSet.UpdatingTableInfo.TableName + ''' ) AND (  '+
    '(NOT Fld.RDB$COMPUTED_BLR IS NULL)  OR '+
    '(NOT Rel.RDB$DEFAULT_SOURCE IS NULL) OR '+
    '(NOT Fld.RDB$DEFAULT_SOURCE IS NULL))';

  MonitorClass := TDASQLMonitorClass(TDBAccessUtils.SQLMonitorClass(UsedConnection));
  if MonitorClass.HasMonitor or FDataSet.Debug then
    MonitorClass.SQLExecute(FDataSet, UpdateQuery.SQL.Text, UpdateQuery.Params, 'Get Default Values', MessageID, True);

  UpdateQuery.Execute;
  while not UpdateQuery.Eof do begin
    FieldName := VarToStr(UpdateQuery.Fields[0].Value);
    FieldDesc := GetIRecordSet.FindField(FieldName) as TIBCFieldDesc;
    if FieldDesc <> nil then begin
      Field := FDataSet.GetField(FieldDesc);
      if Field <> nil then begin
        //1. Check Read Only
        if not UpdateQuery.Fields[1].IsNull then
          Field.ReadOnly := True
        else
        //2. Fill field's Default Expression
        //if DefaultValues then
          if not UpdateQuery.Fields[2].IsNull then
            PrepareDefault(GetFieldValue(UpdateQuery.Fields[2]), Field)
          else
            if not UpdateQuery.Fields[3].IsNull then
              PrepareDefault(GetFieldValue(UpdateQuery.Fields[3]), Field);
      end;
    end;
    UpdateQuery.Next;
  end;

  if MonitorClass.HasMonitor or FDataSet.Debug then
    MonitorClass.SQLExecute(FDataSet, UpdateQuery.SQL.Text, UpdateQuery.Params, 'Get Default Values', MessageID, False);
end;

function TCustomIBCDataSetService.GetRecCount: integer;
var
  i: integer;
  MonitorClass: TDASQLMonitorClass;
  MessageID: cardinal;
  UpdateQuery: TCustomDADataSet;
  s: string;
begin
  Result := 0;
  s := Trim(FDataSet.SQLRecCount.Text);
  if s = '' then
    s := FSQLGenerator.GenerateRecCountSQL;
  if s = '' then
    Exit;

  FUpdater.CheckUpdateQuery(stQuery);
  UpdateQuery := TCustomDADataSet(FUpdater.UpdateQuery);
  UpdateQuery.SQL.Text := s;

  for i := 0 to UpdateQuery.Params.Count - 1 do
    if (FDataSet.Params[i].Name = '') then
      UpdateQuery.Params[i].Assign(FDataSet.Params[i])
    else
      UpdateQuery.Params[i].Assign(FDataSet.ParamByName(UpdateQuery.Params[i].Name));

  MonitorClass := TDASQLMonitorClass(TDBAccessUtils.SQLMonitorClass(UsedConnection));
  if MonitorClass.HasMonitor or FDataSet.Debug then
    MonitorClass.SQLExecute(FDataSet, s, UpdateQuery.Params, 'Get RecordCount', MessageID, True);

  UpdateQuery.Execute;

  Result := UpdateQuery.Fields[0].AsInteger;

  if MonitorClass.HasMonitor or FDataSet.Debug then
    MonitorClass.SQLExecute(FDataSet, s, UpdateQuery.Params, 'Get RecordCount', MessageID, False);
end;

function TCustomIBCDataSetService.DetectCanModify: boolean;
begin
  Result := (inherited DetectCanModify or
    not (FDataSet.ReadOnly or FDataSet.UniDirectional) and
    ((FDataSet.SQLInsert.Count > 0) or
    (FDataSet.SQLUpdate.Count > 0) or
    (FDataSet.SQLDelete.Count > 0) or
    FIsAnyFieldCanBeModified));
end;

function TCustomIBCDataSetService.GetDBKeyList(const TableName, IndexName: string): string;
var
  MetaData: TDAMetaData;
  Info: TSQLObjectInfo;
  ServerIndexName: string;
begin
  BeginConnection; // GetCurrentSchema requires an active connection
  try
    IBCSQLInfo.SplitObjectName(TableName, Info);
    if Info.Schema = '' then
      Info.Schema := GetCurrentSchema;

    MetaData := TDAMetaData.Create(nil);
    try
      MetaData.Connection := UsedConnection;
      TDBAccessUtils.SetTransaction(MetaData, TDBAccessUtils.UsedTransaction(FDataSet));
      MetaData.MetaDataKind := 'constraints';
      MetaData.Restrictions.Text := 'table_catalog=' + Info.Catalog +
        #13#10'table_schema=' + Info.Schema +
        #13#10'table_name=' + Info.Name +
        #13#10'constraint_type=primary key';
      MetaData.Open;
      if not MetaData.Eof then
        ServerIndexName := VarToStr(MetaData.FieldByName('INDEX_NAME').Value)
      else
        ServerIndexName := IndexName;
      MetaData.Close;
    finally
      MetaData.Free;
    end;

    Result := inherited GetDBKeyList(TableName, ServerIndexName);
  finally
    EndConnection;
  end;
end;

{ TCustomIBCFieldTypeMap }

class function TCustomIBCFieldTypeMap.GetDataType(FieldType: TFieldType): integer;
begin
  if Integer(FieldType) = Integer(ftFixedWideChar) then
    Result := dtFixedWideChar
  else
  if FieldType = ftFixedChar then
    Result := dtFixedChar
  else
  if FieldType = ftADT then
    Result := dtArray
  else
  if FieldType in [ftReference, ftDataSet, ftVariant] then
    Result := 0
  else
    Result := inherited GetDataType(FieldType);
end;

class function TCustomIBCFieldTypeMap.GetFieldType(DataType: Word): TFieldType;
begin
  case DataType of
    dtFixedChar:
      Result := ftFixedChar;
    dtFixedWideChar:
      Result := TFieldType(ftFixedWideChar);
  else
    Result := inherited GetFieldType(DataType);
  end;
end;

{ TCustomIBCDumpProcessor }

function TCustomIBCDumpProcessor.GetFieldValueForDump(Field: TField): string;
const
  BoolStrs: array [boolean] of string = ('0', '1');
begin
  if not Field.IsNull and (Field.DataType = ftBoolean) then
    Result := BoolStrs[Field.AsBoolean]
  else
    Result := inherited GetFieldValueForDump(Field);
end;

end.
