{$IFNDEF CLR}
{$I PgDac.inc}
unit PgSQLGeneratorUni;
{$ENDIF}

interface

uses
  Classes, Variants,
{$IFDEF CLR}
  System.Runtime.InteropServices, System.Text,
{$ELSE}
  CLRClasses,
{$ENDIF}
  CRTypes, DASQLGenerator, MemData, CRAccess;

type
  // must be sync with types in PgAccess
  _TSequenceMode = (_smInsert, _smPost);

  TPgParamInfo = class(TDAParamInfo)
  public
    SB: StringBuilder;
    Position: integer;
    EndPosition: integer;
  end;

  TCustomPgSQLGenerator = class(TDASQLGenerator)
  private
    FSequenceMode: _TSequenceMode;
    FKeyGeneratorField: TFieldDesc;
    FSequenceRequestUseReturning: boolean;

  protected
    procedure Clear; override;
    function GetParamInfoClass: TDAParamInfoClass; override;

    procedure AddParam(SB: StringBuilder; FieldDesc: TFieldDesc;
      const StatementType: _TStatementType;
      const ParamType: _TParamType;
      Index: integer = -1;
      Old: boolean = False); override;

    function FieldIsNull(FieldDesc: TCRFieldDesc; OldValue: boolean; Data: TData; OldRecBuf, NewRecBuf: IntPtr): boolean; override;
    function FieldModified(FieldDesc: TCRFieldDesc; Data: TData; OldRecBuf, NewRecBuf: IntPtr): boolean; override;

    procedure AddFieldToInsertSQL(FieldDesc: TCRFieldDesc; const Index: integer = -1); override;

    procedure GenerateInsertSQL(
      const KeyAndDataFields: TKeyAndDataFields;
      const ModifiedFieldsOnly: boolean;
      const Index: integer = -1); override;
    procedure GenerateUpdateSQL(
      const KeyAndDataFields: TKeyAndDataFields;
      const ModifiedFieldsOnly: boolean;
      const Index: integer = -1); override;
    procedure GenerateLockSQL(
      const KeyAndDataFields: TKeyAndDataFields;
      const Index: integer = -1); override;

  public
    function GenerateRecCountSQL(UseBaseSQL: boolean = False): string; override;

    function GetParamOffset(const Index: integer): integer;

    property SequenceMode: _TSequenceMode read FSequenceMode write FSequenceMode;
    property SequenceRequestUseReturning: boolean read FSequenceRequestUseReturning write FSequenceRequestUseReturning;
  end;

implementation

uses
  SysUtils, DAConsts, CRParser,
{$IFNDEF UNIDACPRO}
  PgConsts, PgParser, PgObjects, PgClasses;
{$ELSE}
  PgConstsUni, PgParserUni, PgObjectsUni, PgClassesUni;
{$ENDIF}

{ TCustomPgSQLGenerator }

procedure TCustomPgSQLGenerator.Clear;
begin
  inherited;
  FKeyGeneratorField := nil;
end;

function TCustomPgSQLGenerator.GetParamOffset(const Index: integer): integer;
var
  ParamInfo: TPgParamInfo;
begin
  ParamInfo := TPgParamInfo(FParamsInfo.Items[Index]);
  Result := 0;
  if ParamInfo.SB = FFldSB then
    Result := FHeaderSB.Length
  else
  if ParamInfo.SB = FFldParamSB then
    Result := FHeaderSB.Length + FFldSB.Length + FMiddleSB.Length
  else
  if ParamInfo.SB = FCondSB then
    Result := FHeaderSB.Length + FFldSB.Length + FMiddleSB.Length + FFldParamSB.Length
  else
    Assert(False);
end;

function TCustomPgSQLGenerator.GetParamInfoClass: TDAParamInfoClass;
begin
  Result := TPgParamInfo;
end;

procedure TCustomPgSQLGenerator.AddParam(SB: StringBuilder; FieldDesc: TFieldDesc;
  const StatementType: _TStatementType;
  const ParamType: _TParamType;
  Index: integer = -1; // only for CachedUpdates
  Old: boolean = False);
var
  ParamName: string;
  ParamInfo: TPgParamInfo;
  p: integer;
  CheckQuoteNeeded: boolean;
begin
  ParamName := FieldDesc.Name;
  CheckQuoteNeeded := True;

  if Old then
    ParamName := 'Old_' + ParamName;

  if Index > - 1 then
    ParamName := IndexedPrefix + IntToStr(Index) + '_' + ParamName;

  if QuoteNames or
    CheckQuoteNeeded and SQLInfo.QuotesNeeded(FieldDesc.Name)
  then
    ParamName := SQLInfo.Quote(ParamName);

  p := SB.Length + 1;
  if not IsSubstituteParamName then begin
    SB.Append(':');
    SB.Append(ParamName);
  end
  else
    SB.Append('$' + IntToStr(FParamsInfo.Count + 1));

  ParamInfo := TPgParamInfo(FParamsInfo.Add);
  ParamInfo.FieldDesc := FieldDesc;
  ParamInfo.Old := Old;
  ParamInfo.ParamIndex := Index;
  ParamInfo.ParamName := ParamName;
  ParamInfo.ParamType := ParamType;
  ParamInfo.Position := p;
  ParamInfo.EndPosition := SB.Length + 1;
  ParamInfo.SB := SB;
end;

function TCustomPgSQLGenerator.FieldIsNull(FieldDesc: TCRFieldDesc; OldValue: boolean; Data: TData; OldRecBuf, NewRecBuf: IntPtr): boolean;
var
  Obj: TSharedObject;
begin
  if FieldDesc = FKeyGeneratorField then
    Result := False
  else begin
    Result := inherited FieldIsNull(FieldDesc, OldValue, Data, OldRecBuf, NewRecBuf);

    if Result and (FieldDesc.DataType = dtPgLargeObject) then begin
      if OldValue then
        Obj := Data.GetObject(FieldDesc, OldRecBuf)
      else
        Obj := Data.GetObject(FieldDesc, NewRecBuf);

      Result := TPgSQLLargeObject(Obj).OID = 0;
    end;
  end;
end;

function TCustomPgSQLGenerator.FieldModified(FieldDesc: TCRFieldDesc; Data: TData; OldRecBuf, NewRecBuf: IntPtr): boolean;
var
  Obj: TSharedObject;
begin
  if (FieldDesc.DataType = dtPgLargeObject) and not InCacheProcessing then begin
    Obj := Data.GetObject(FieldDesc, NewRecBuf);
    Result := TPgObjectsUtils.GetLargeObjectOIDChanged(TPgSQLLargeObject(Obj));
  end
  else
    Result := False;

  Result := Result or inherited FieldModified(FieldDesc, Data, OldRecBuf, NewRecBuf);
end;

procedure TCustomPgSQLGenerator.AddFieldToInsertSQL(FieldDesc: TCRFieldDesc;
  const Index: integer = -1);
begin
  if FieldDesc = FKeyGeneratorField then begin
    if FFldSB.Length > 0 then begin
      FFldSB.Append(', ');
      FFldParamSB.Append(', ');
    end;
    FFldSB.Append(FieldDesc.ActualNameQuoted(SQLInfo, QuoteNames));

    FFldParamSB.Append(Format('NEXTVAL(%s)', [PgSQLInfo.ToStringConst(FKeySequence)]));
  end
  else
    inherited;
end;

procedure TCustomPgSQLGenerator.GenerateInsertSQL(
  const KeyAndDataFields: TKeyAndDataFields;
  const ModifiedFieldsOnly: boolean;
  const Index: integer = -1);
var
  i: integer;
  ReturnSB: StringBuilder;
  FieldDesc: TCRFieldDesc;
begin
  if FSequenceRequestUseReturning then
    FKeyGeneratorField := FIRecordSet.KeyGeneratorField
  else
    FKeyGeneratorField := nil;

  inherited;

  if FFldSB.Length = 0 then begin
    Clear;
    FHeaderSB.Append('INSERT INTO ');
    FHeaderSB.Append(SQLInfo.NormalizeName(FTableInfo.TableNameFull, QuoteNames));
    FHeaderSB.Append(' DEFAULT VALUES');
  end;

  if FDMLRefresh or (FKeyGeneratorField <> nil) then begin
    ReturnSB := StringBuilder.Create(100);
    try
      for i := 0 to High(KeyAndDataFields.DataFieldDescs) do begin
        FieldDesc := KeyAndDataFields.DataFieldDescs[i];
        if (FDMLRefresh and
          not (FieldDesc.DataType in [dtMemo, dtWideMemo, dtBlob, dtPgLargeObject])) or
          (FKeyGeneratorField = FieldDesc)
        then begin
          if ReturnSB.Length > 0 then
            ReturnSB.Append(', ');
          ReturnSB.Append(FieldDesc.ActualNameQuoted(SQLInfo, QuoteNames));
        end;
      end;

      if ReturnSB.Length > 0 then begin
        FFooterSB.Append(#$D#$A'RETURNING'#$D#$A'  ');
        FFooterSB.Append(ReturnSB);
      end;
    finally
      ReturnSB.Free;
    end;
  end;
end;

procedure TCustomPgSQLGenerator.GenerateUpdateSQL(const KeyAndDataFields: TKeyAndDataFields;
  const ModifiedFieldsOnly: boolean; const Index: integer = -1);
var
  i: integer;
  ReturnSB: StringBuilder;
  FieldDesc: TCRFieldDesc;
begin
  inherited GenerateUpdateSQL(KeyAndDataFields, ModifiedFieldsOnly, Index);

  if FFldSB.Length = 0 then
    Exit;

  if FDMLRefresh then begin
    ReturnSB := StringBuilder.Create(100);
    try
      for i := 0 to High(KeyAndDataFields.DataFieldDescs) do begin
        FieldDesc := KeyAndDataFields.DataFieldDescs[i];
        if not (FieldDesc.DataType in [dtMemo, dtWideMemo, dtBlob, dtPgLargeObject])
        then begin
          if ReturnSB.Length > 0 then
            ReturnSB.Append(', ');
          ReturnSB.Append(FieldDesc.ActualNameQuoted(SQLInfo, QuoteNames));
        end;
      end;

      if ReturnSB.Length <> 0 then begin
        FFooterSB.Append(#$D#$A'RETURNING'#$D#$A'  ');
        FFooterSB.Append(ReturnSB);
      end;
    finally
      ReturnSB.Free;
    end;
  end;
end;

procedure TCustomPgSQLGenerator.GenerateLockSQL(
  const KeyAndDataFields: TKeyAndDataFields;
  const Index: integer = -1);
begin
  FHeaderSB.Append('SELECT * FROM ');
  FHeaderSB.Append(SQLInfo.NormalizeName(FTableInfo.TableNameFull, QuoteNames));
  FMiddleSB.Append(#$D#$A'WHERE'#$D#$A'  ');
  GenerateConditions(FCondSB, _stLock, KeyAndDataFields, Index);
  FFooterSB.Append(#$D#$A'FOR UPDATE NOWAIT');
end;

function TCustomPgSQLGenerator.GenerateRecCountSQL(UseBaseSQL: boolean): string;
var
  Parser: TPgParser;
begin
  Clear;
  if UseBaseSQL then
    Result := FService.BaseSQL
  else
    Result := FService.FinalSQL;
  Result := SQLSetOrderBy(Result, '');
  Parser := TPgParser.Create(Result);
  try
    if Parser.ToLexem(lxFOR) <> lcEnd then begin
      Result := copy(Result, 1, Parser.CurrPos - 3);
    end;
  finally
    Parser.Free;
  end;
  Result := 'SELECT count(*) FROM (' + DALineSeparator + Result + DALineSeparator + ') t';
  FHeaderSB.Append(Result);
end;

end.
