unit XData.Criteria.CriterionVisitor;

interface

uses
  Generics.Collections,
  Aurelius.Criteria.Base,
  XData.Criteria.ProjectionVisitor,
  XData.Query.Expression;

type
  TQueryCriterionVisitor = class(TStackQueryVisitor<TCustomCriterion>)
  strict private
    FProjectionVisitor: TQueryProjectionVisitor;
  public
    procedure NullLiteral(const Expr: TNullLiteralExpression); override;
    procedure StringLiteral(const Expr: TStringLiteralExpression); override;
    procedure DateTimeLiteral(const Expr: TDateTimeLiteralExpression); override;
    procedure GuidLiteral(const Expr: TGuidLiteralExpression); override;
    procedure DoubleLiteral(const Expr: TDoubleLiteralExpression); override;
    procedure IntegerLiteral(const Expr: TIntegerLiteralExpression); override;
    procedure Int64Literal(const Expr: TInt64LiteralExpression); override;
    procedure BooleanLiteral(const Expr: TBooleanLiteralExpression); override;
    procedure EnumerationLiteral(const Expr: TEnumerationLiteralExpression); override;
    procedure Logical(const Expr: TLogicalExpression); override;
    procedure Comparison(const Expr: TComparisonExpression); override;
    procedure Arithmetic(const Expr: TArithmeticExpression); override;
    procedure ArithmeticUnary(const Expr: TArithmeticUnaryExpression); override;
    procedure Parenthesis(const Expr: TParenthesisExpression); override;
    procedure Member(const Expr: TMemberExpression); override;
    procedure MethodCall(const Expr: TMethodCallExpression); override;
    procedure BooleanUnary(const Expr: TBooleanUnaryExpression); override;
    procedure ResolvedMember(const Expr: TResolvedMemberExpression); override;
  public
    constructor Create;
    destructor Destroy; override;
  end;

implementation

uses
  Aurelius.Criteria.Expression,
  Aurelius.Criteria.Projections,
  XData.Query.Exceptions;

{ TQueryCriterionVisitor }

procedure TQueryCriterionVisitor.Arithmetic(const Expr: TArithmeticExpression);
begin
  raise EQueryUnsupportedCondition.Create(Expr);
end;

procedure TQueryCriterionVisitor.ArithmeticUnary(const Expr: TArithmeticUnaryExpression);
begin
  raise EQueryUnsupportedCondition.Create(Expr);
end;

procedure TQueryCriterionVisitor.BooleanLiteral(const Expr: TBooleanLiteralExpression);
begin
  if Expr.Value then
    Push(TExpression.Eq(TProjections.Literal<integer>(0), TProjections.Literal<integer>(0)))
  else
    Push(TExpression.Lt(TProjections.Literal<integer>(0), TProjections.Literal<integer>(0)))
end;

procedure TQueryCriterionVisitor.BooleanUnary(const Expr: TBooleanUnaryExpression);
begin
  Expr.Expression.Accept(Self);
  case Expr.Oper.OperatorType of
    opNot:
      Push(TExpression.Not_(Pop));
  else
    raise EQueryInvalidOperatorError.Create(Expr.Oper.Name);
  end;
end;

procedure TQueryCriterionVisitor.Comparison(const Expr: TComparisonExpression);
var
  Left, Right: TSimpleProjection;
begin
  Expr.Right.Accept(FProjectionVisitor);
  Expr.Left.Accept(FProjectionVisitor);

  if FProjectionVisitor.Peek is TNullProjection then
  begin
    {$IFDEF AUTOREFCOUNT}
    FProjectionVisitor.Pop;
    {$ELSE}
    FProjectionVisitor.Pop.Free;
    {$ENDIF}
    case Expr.Oper.OperatorType of
      opEq:
        Push(TExpression.IsNull(FProjectionVisitor.Pop));
      opNe:
        Push(TExpression.IsNotNull(FProjectionVisitor.Pop));
    else
      raise EQueryInvalidOperatorError.Create(Expr.Oper.Name);
    end;
  end
  else
  if FProjectionVisitor.PeekPrevious is TNullProjection then
  begin
    case Expr.Oper.OperatorType of
      opEq:
        Push(TExpression.IsNull(FProjectionVisitor.Pop));
      opNe:
        Push(TExpression.IsNotNull(FProjectionVisitor.Pop));
    else
      raise EQueryInvalidOperatorError.Create(Expr.Oper.Name);
    end;
    {$IFDEF AUTOREFCOUNT}
    FProjectionVisitor.Pop;
    {$ELSE}
    FProjectionVisitor.Pop.Free;
    {$ENDIF}
  end else
  begin
    Left := FProjectionVisitor.Pop;
    Right := FProjectionVisitor.Pop;
    case Expr.Oper.OperatorType of
      opEq:
        Push(TExpression.Eq(Left, Right));
      opNe:
        Push(TExpression.Not_(TExpression.Eq(Left, Right)));
      opLt:
        Push(TExpression.Lt(Left, Right));
      opGt:
        Push(TExpression.Gt(Left, Right));
      opLe:
        Push(TExpression.Le(Left, Right));
      opGe:
        Push(TExpression.Ge(Left, Right));
      opLike:
        Push(TExpression.Like(Left, Right));
      opILike:
        Push(TExpression.ILike(Left, Right));
    else
      Left.Free;
      Right.Free;
      raise EQueryInvalidOperatorError.Create(Expr.Oper.Name);
    end;
  end;
end;

constructor TQueryCriterionVisitor.Create;
begin
  inherited Create;
  FProjectionVisitor := TQueryProjectionVisitor.Create;
end;

procedure TQueryCriterionVisitor.DateTimeLiteral(const Expr: TDateTimeLiteralExpression);
begin
  raise EQueryUnsupportedCondition.Create(Expr);
end;

destructor TQueryCriterionVisitor.Destroy;
begin
  FProjectionVisitor.Free;
  inherited;
end;

procedure TQueryCriterionVisitor.DoubleLiteral(const Expr: TDoubleLiteralExpression);
begin
  raise EQueryUnsupportedCondition.Create(Expr);
end;

procedure TQueryCriterionVisitor.EnumerationLiteral(
  const Expr: TEnumerationLiteralExpression);
begin
  raise EQueryUnsupportedCondition.Create(Expr);
end;

procedure TQueryCriterionVisitor.GuidLiteral(const Expr: TGuidLiteralExpression);
begin
  raise EQueryUnsupportedCondition.Create(Expr);
end;

procedure TQueryCriterionVisitor.Int64Literal(const Expr: TInt64LiteralExpression);
begin
  raise EQueryUnsupportedCondition.Create(Expr);
end;

procedure TQueryCriterionVisitor.IntegerLiteral(const Expr: TIntegerLiteralExpression);
begin
  raise EQueryUnsupportedCondition.Create(Expr);
end;

procedure TQueryCriterionVisitor.Logical(const Expr: TLogicalExpression);
var
  Left, Right: TCustomCriterion;
begin
  Expr.Right.Accept(Self);
  Expr.Left.Accept(Self);
  case Expr.Oper.OperatorType of
    opAnd:
      begin
        Left := Pop;
        Right := Pop;
        Push(TExpression.And_(Left, Right));
      end;
    opOr:
      begin
        Left := Pop;
        Right := Pop;
        Push(TExpression.Or_(Left, Right));
      end
  else
    raise EQueryInvalidOperatorError.Create(Expr.Oper.Name);
  end;
end;

procedure TQueryCriterionVisitor.Member(const Expr: TMemberExpression);
begin
  raise EQueryUnsupportedCondition.Create(Expr);
end;

procedure TQueryCriterionVisitor.MethodCall(const Expr: TMethodCallExpression);
var
  I: integer;
  Args: TArray<TSimpleProjection>;
begin
  if not Expr.Method.IsBoolean then
    raise EQueryNoBooleanMethodError.Create(Expr);

  // Process input arguments and transform them into projections
  for I := Expr.Arguments.Count - 1 downto 0 do
    Expr.Arguments[I].Accept(FProjectionVisitor);

  // Flll the projection array for arguments
  SetLength(Args, Expr.Arguments.Count);
  for I := 0 to Length(Args) - 1 do
    Args[I] := FProjectionVisitor.Pop;

  // Push the SQL function criterion
  Push(TSQLFunctionCriterion.Create(Expr.Method.SQLFunctionName, Args));
end;

procedure TQueryCriterionVisitor.NullLiteral(const Expr: TNullLiteralExpression);
begin
  raise EQueryUnsupportedCondition.Create(Expr);
end;

procedure TQueryCriterionVisitor.Parenthesis(const Expr: TParenthesisExpression);
begin
  Expr.Expression.Accept(Self);
end;

procedure TQueryCriterionVisitor.ResolvedMember(const Expr: TResolvedMemberExpression);
begin
  raise EQueryUnsupportedCondition.Create(Expr);
end;

procedure TQueryCriterionVisitor.StringLiteral(const Expr: TStringLiteralExpression);
begin
  raise EQueryUnsupportedCondition.Create(Expr);
end;

end.
