unit Aurelius.Commands.AbstractCommandPerformer;

{$I Aurelius.inc}

interface

uses
  Generics.Collections, DB,
  Aurelius.Commands.AbstractSqlPerformer,
  Aurelius.Drivers.Interfaces,
  Aurelius.Mapping.Metadata,
  Aurelius.Sql.BaseTypes,
  Aurelius.Sql.Interfaces;

type
  TAbstractCommandPerformer = class abstract(TAbstractSQLPerformer)
  strict private
    type
      TCommandPerformerWrapper = class(TInterfacedObject, ICommandPerformer)
      private
        FCommand: TAbstractCommandPerformer;
      public
        constructor Create(ACommand: TAbstractCommandPerformer);
        function ExecuteQuery(SQL: string; Params: TList<TDBParam>): IDBResultSet;
        function Execute(SQL: string; Params: TList<TDBParam>): Integer;
        function CreateSQLTable: TSQLTable;
      end;
  strict private
    FDiscriminatorValues: TArray<Variant>;
    FDiscriminatorColumn: TColumn;
    FAlias: string;
    FHasJoinedTablesStrategy: boolean;
    FPKColumns: TArray<TColumn>;
    FColumns: TList<TColumn>;
  strict protected
    function IsIdColumn(Col: TColumn): boolean;
    function GetCommandPerformer: ICommandPerformer;
    property DiscriminatorValues: TArray<Variant> read FDiscriminatorValues;
    property HasJoinedTablesStrategy: boolean read FHasJoinedTablesStrategy;
  public
    constructor Create; override;
    procedure SetClass(Clazz: TClass); override;
    function HasPrimaryJoinColumn: Boolean;
    property Alias: string read FAlias write FAlias;
    property DiscriminatorColumn: TColumn read FDiscriminatorColumn;
    property Columns: TList<TColumn> read FColumns;
    property PKColumns: TArray<TColumn> read FPKColumns;
    function CreateSQLTable: TSQLTable; virtual;
  end;

  TSQLPerformer = class(TAbstractSQLPerformer)
  public
    procedure ExecuteSQL(const SQL: string);
  end;

implementation

uses
  Aurelius.Global.Utils,
  Aurelius.Sql.Exceptions;

{ TAbstractCommandPerformer }

constructor TAbstractCommandPerformer.Create;
begin
  inherited;
  FAlias := 'A';
end;

function TAbstractCommandPerformer.CreateSQLTable: TSQLTable;
var
  ATable: TMetaTable;
begin
  ATable := Explorer.GetTable(Clazz);
  Result := TSQLTable.Create(ATable.Name, ATable.Schema, FAlias);
end;

function TAbstractCommandPerformer.GetCommandPerformer: ICommandPerformer;
begin
  Result := TCommandPerformerWrapper.Create(Self);
end;

function TAbstractCommandPerformer.HasPrimaryJoinColumn: Boolean;
begin
  Result := Length(Explorer.GetPrimaryJoinColumns(Clazz)) > 0;
end;

function TAbstractCommandPerformer.IsIdColumn(Col: TColumn): boolean;
var
  C: TColumn;
begin
  for C in Explorer.GetIdColumns(Self.Clazz) do
    if C = Col then
      Exit(true);
  Result := false;
end;

procedure TAbstractCommandPerformer.SetClass(Clazz: TClass);
var
  SubClasses: TList<TClass>;
  c: integer;
  SubClass: TClass;
begin
  inherited;

  FHasJoinedTablesStrategy := Explorer.HasInheritance(Clazz) and
    (Explorer.GetInheritanceStrategy(Clazz) = TInheritanceStrategy.JoinedTables);

  if HasJoinedTablesStrategy then
  begin
    FColumns := Explorer.GetColumns(Clazz, False, False);
    FPKColumns := Explorer.GetPrimaryJoinColumns(Clazz);

    if Length(FPKColumns) = 0 then
      FPKColumns := Explorer.GetIdColumns(Clazz);
  end
  else
  begin
    FColumns := Explorer.GetColumns(Clazz, True, True);
    FPKColumns := Explorer.GetIdColumns(Clazz);
  end;

  FDiscriminatorColumn := Explorer.GetDiscriminatorColumn(Clazz, True);

  // If we have a discriminator, then we must filter the select using the discriminator values
  // TODO: One exception is if current class is the root class for the hierarchy. In this case we won't filter
  // and we will accept all rows from the database (for optimization only).
  if FDiscriminatorColumn <> nil then
  begin
    // Get the discriminator values for all descendant classes
    SubClasses := TList<TClass>.Create;
    try
      for SubClass in Explorer.Hierarchy.GetAllSubClasses(Clazz) do
        SubClasses.Add(SubClass);
      SetLength(FDiscriminatorValues, SubClasses.Count + 1);
      FDiscriminatorValues[0] := TUtils.ValueToVariant(
        Explorer.GetDiscriminatorValue(Clazz));
      for c := 0 to SubClasses.Count - 1 do
        FDiscriminatorValues[c + 1] := TUtils.ValueToVariant(
          Explorer.GetDiscriminatorValue(SubClasses[c]));
    finally
      SubClasses.Free;
    end;
  end else
    SetLength(FDiscriminatorValues, 0);

  Explorer.CheckValidEntity(Clazz);
end;

{ TSQLPerfomer }

procedure TSQLPerformer.ExecuteSQL(const SQL: string);
begin
  Execute(SQL, nil);
end;

{ TAbstractCommandPerformer.TCommandPerformerWrapper }

constructor TAbstractCommandPerformer.TCommandPerformerWrapper.Create(
  ACommand: TAbstractCommandPerformer);
begin
  FCommand := ACommand;
end;

function TAbstractCommandPerformer.TCommandPerformerWrapper.CreateSQLTable: TSQLTable;
begin
  Result := FCommand.CreateSQLTable;
end;

function TAbstractCommandPerformer.TCommandPerformerWrapper.Execute(SQL: string;
  Params: TList<TDBParam>): Integer;
begin
  Result := FCommand.Execute(SQL, Params);
end;

function TAbstractCommandPerformer.TCommandPerformerWrapper.ExecuteQuery(SQL: string;
  Params: TList<TDBParam>): IDBResultSet;
begin
  Result := FCommand.ExecuteQuery(SQL, Params);
end;

end.
