unit ABSExpressions;

interface

{$I ABSVer.inc}

uses Classes, SysUtils, DB,
{$IFDEF D6H}
     Variants,
     DateUtils,
{$ELSE}
     ABSD4Routines,
{$ENDIF}

     {$IFDEF DEBUG_LOG}
     ABSDebug,
     {$ENDIF}

     ABSBase,
     ABSVariant,
     ABSLexer,
     ABSExcept,
     ABSConst,
     ABSTypes,
     ABSConverts,
     ABSRelationalAlgebra,
     ABSDateFormat;


type

 TABSExprNodeType = (entField, entConst, entOperator, entSet, entNull);
 TSign = (sgnUndefined, sgnPlus, sgnMinus);

 TABSQuantifier = (qUNDEFINED, qALL, qANY);


  // forward declarations
  TABSExprNode = class;
  TABSExprNodeComparison = class;
  TABSExprNodeArray = array of TABSExprNode;

////////////////////////////////////////////////////////////////////////////////
//
// TABSExpression
//
////////////////////////////////////////////////////////////////////////////////


  TABSExpression = class (TObject)
   private
    LLex: TABSLexer;   // lexer with expression to parse
    Token: TToken;     // current token
    LCursor: TABSCursor;
    LDataset: TDataset;
    FRootExprNode: TABSExprNode;
    FCaseInsensitive: Boolean;
    FPartialKey: Boolean;
    F3ValueLogic: Boolean;
    FInMemory:    Boolean;
    FDatabaseName: string;
    FSessionName: string;
   private
    // gets current token
    function GetCurrentToken: Boolean;
    // gets next token
    function GetNextToken: Boolean; overload;
    // gets next token
    //function GetNextToken(NativeErrorCode: integer): Boolean; overload;
    // gets token and lokks at next token with check for token type restrictions
    function GetNextToken(PermittedTypes: TTokenTypes;
                          RaiseExceptions: Boolean = False): Boolean; overload;

   private
    // saves internal state (to restore in case of not successful forward parsing)
    procedure SaveState(var SavedTokenNo: integer);// var SavedRootNode: TABSExprNode);
    // restores internal state (in case of not successful forward parsing)
    procedure RestoreState(SavedTokenNo: integer);// SavedRootNode: TABSExprNode);

    // parses <,>,=,<>,>=,<=
    function ParseCompOp: TABSDataOperator;
    // parses ALL, ANY, SOME
    function ParseQuantifier: TABSQuantifier;
    // parses <row value constructor element>
    function ParseRowValueConstructorElement: TABSExprNode;
    // parses <row subquery>
    function ParseRowSubquery(MustHaveOneRow: Boolean; MustHaveOneField: Boolean=True): TABSExprNode;
    // parses <row value constructor element> | <row subquery>
    function ParseRowValueConstructor: TABSExprNode;
    // parses <row value constructor> <comp op> <quantifier> <row value constructor>
    function ParseQuantifiedComparisonPredicate: TABSExprNode;
    // parses <row value constructor> <comp op> <row value constructor>
    function ParseComparisonPredicate: TABSExprNode;
    // parses <match value> [ NOT ] LIKE <pattern> [ ESCAPE <escape character> ]
    function ParseLikePredicate: TABSExprNode;
    // parses <row value constructor> IS [ NOT ] NULL
    function ParseNullPredicate: TABSExprNode;
    // parses <between predicate>
    function ParseBetweenPredicate: TABSExprNode;
    // parses <in predicate>
    function ParseInPredicate: TABSExprNode;
    // parses <exists predicate>
    function ParseExistsPredicate: TABSExprNode;
    // parses <true/false>
    function ParseTrueFalseConst: TABSExprNode;
    // parses <NULL> const
    function ParseNullConst: TABSExprNode;
    // parses <comparison predicate> | <between predicate> | <in predicate>  |
    // <like predicate>  | <null predicate> | <quantified comparison predicate> |
    // <exists predicate> | <unique predicate> | <match predicate> | <overlaps predicate>
    function ParsePredicate: TABSExprNode;
    // parses <predicate>  | <left paren> <search condition> <right paren> | <row value constructor>
    function ParseBooleanPrimary: TABSExprNode;
    // parses <boolean primary> [ IS [ NOT ] <truth value> ]
    function ParseBooleanTest: TABSExprNode;
    // parses [ NOT ] <boolean test>
    function ParseBooleanFactor: TABSExprNode;
    // parses <boolean factor> | <boolean term> AND <boolean factor>
    function ParseBooleanTerm: TABSExprNode;
    // parses <boolean term> | <search condition> OR <boolean term>
    function ParseSearchCondition: TABSExprNode;

    // parses <value expression>
    function ParseValueExpression: TABSExprNode;
    // parses <numeric value expression>
    function ParseNumericValueExpression: TABSExprNode;
    // parses <term>
    function ParseTerm: TABSExprNode;
    // parses <factor>
    function ParseFactor: TABSExprNode;
    // parses <sign> ('+' or '-')
    function ParseSign: TSign;
    // parses <numeric primary>
    function ParseNumericPrimary: TABSExprNode;
    // parses <value expression primary>
    function ParseValueExpressionPrimary: TABSExprNode;
    // parses <unsigned value specification>
    function ParseUnsignedValueSpecification: TABSExprNode;
    // parses <unsigned literal>
    function ParseUnsignedLiteral: TABSExprNode;
    // parses <unsigned numeric literal>
    function ParseUnsignedNumericLiteral: TABSExprNode;
    // parses <column reference>
    function ParseColumnReference: TABSExprNode;
    // parses <set function specification>
    function ParseSetFunctionSpecification: TABSExprNode;
    // parses <general set function>
    function ParseGeneralSetFunction: TABSExprNode;
    // parses <cast specification>
    function ParseCastSpecification: TABSExprNode;
    // parses <cast operand>
    function ParseCastOperand: TABSExprNode;

    // parses <numeric value function>
    function ParseNumericValueFunction: TABSExprNode;
    // parses <position expression>
    function ParsePositionExpression: TABSExprNode;
    // parses <extract expression>
    function ParseExtractExpression: TABSExprNode;
    // parses <length expression>
    function ParseLengthExpression: TABSExprNode;
    // parses <LastAutoInc expression>
    function ParseLastAutoIncExpression: TABSExprNode;
    // parses <RowNum expression>
    function ParseRowNumExpression: TABSExprNode;
    // parses <x(y) expression>
    function ParseSingleArgMathExpression(rw: TReservedWord;
                            op: TABSDataOperator; OpText: string): TABSExprNode;
    // parses <ABS expression>
    function ParseABSExpression: TABSExprNode;
    // parses <ACOS expression>
    function ParseACOSExpression: TABSExprNode;
    // parses <ASIN expression>
    function ParseASINExpression: TABSExprNode;
    // parses <ATAN expression>
    function ParseATANExpression: TABSExprNode;
    // parses <CEIL expression>
    function ParseCEILExpression: TABSExprNode;
    // parses <COS expression>
    function ParseCOSExpression: TABSExprNode;
    // parses <EXP expression>
    function ParseEXPExpression: TABSExprNode;
    // parses <FLOOR expression>
    function ParseFLOORExpression: TABSExprNode;
    // parses <LOG expression>
    function ParseLOGExpression: TABSExprNode;
    // parses <POWER expression>
    function ParsePOWERExpression: TABSExprNode;
    // parses <RAND expression>
    function ParseRANDExpression: TABSExprNode;
    // parses <ROUND expression>
    function ParseROUNDExpression: TABSExprNode;
    // parses <SIGN expression>
    function ParseSIGNExpression: TABSExprNode;
    // parses <SIN expression>
    function ParseSINExpression: TABSExprNode;
    // parses <SQR expression>
    function ParseSQRExpression: TABSExprNode;
    // parses <SQRT expression>
    function ParseSQRTExpression: TABSExprNode;
    // parses <math expression>
    function ParseMathExpression: TABSExprNode;

    // parses <string value expression>
    function ParseStringValueExpression: TABSExprNode;
    // parses <character value expression>
    function ParseCharacterValueExpression: TABSExprNode;
    // parses <concatenation>
    function ParseConcatination: TABSExprNode;
    // parses <character factor>
    function ParseCharacterFactor: TABSExprNode;
    // parses <character primary>
    function ParseCharacterPrimary: TABSExprNode;
    // parses <general literal>
    function ParseGeneralLiteral: TABSExprNode;
    // parses <character string literal>
    function ParseCharacterStringLiteral: TABSExprNode;
    // parses <datetime literal>
    function ParseDateTimeLiteral: TABSExprNode;
    // parses <date literal>
    function ParseDateLiteral: TABSExprNode;
    // parses <date string>
    function ParseDateString: TABSExprNode;
    // parses <time literal>
    function ParseTimeLiteral: TABSExprNode;
    // parses <time string>
    function ParseTimeString: TABSExprNode;
    // parses <timestamp literal>
    function ParseTimeStampLiteral: TABSExprNode;
    // parses <timestamp string>
    function ParseTimeStampString: TABSExprNode;
    // parses <string value function>
    function ParseStringValueFunction: TABSExprNode;
    // parses <character value function>
    function ParseCharacterValueFunction: TABSExprNode;

    // parse <datetime value expression>
    function ParseDatetimeValueExpression: TABSExprNode;
    // parse <datetime sysdate function>
    function ParseSysdateFunction: TABSExprNode;
    // parse <datetime CURRENT_TIME function>
    function ParseCurrentTimeFunction: TABSExprNode;
    // parse <datetime CURRENT_DATE function>
    function ParseCurrentDateFunction: TABSExprNode;
    // parse <datetime TODATE function>
    function ParseToDateFunction: TABSExprNode;

    // parse <interval value expression>
    function ParseIntervalValueExpression: TABSExprNode;

    // parse <case expression>
    function ParseCaseExpression: TABSExprNode;
    // parse <case abbreviation>
    function ParseCaseAbbreviation: TABSExprNode;
    // parse <coalesce>
    function ParseCoalesce: TABSExprNode;
    // parse <case specification>
    function ParseCaseSpecification: TABSExprNode;
    // parse <simple case>
    function ParseSimpleCase: TABSExprNode;
    // parse <searched case>
    function ParseSearchedCase: TABSExprNode;

    // parse Blob Expression
    function ParseBlobExpression: TABSExprNode;
    // parse MimeToBin Function
    function ParseMimeToBinFunction: TABSExprNode;



   private
    procedure Clear;
   public
    // Constructors
    constructor Create; overload;
    // creates
    constructor Create(RootNode: TABSExprNode); overload;
    //constructor Create(Lexer: TABSLexer); overload;
    // Destructor
    destructor Destroy; override;
   public
    // Parsing for Locate
    procedure ParseForLocate(
                              Cursor: TABSCursor;
                              FieldNames: string;
                              KeyValues: Variant;
                              CaseInsensitive: boolean = true;
                              PartialKey: boolean = false
                            );
    // Parsing for Filter
    procedure ParseForFilter(
                              Cursor: TABSCursor;
                              Filter: string;
                              CaseInsensitive: boolean = true;
                              PartialKey: boolean = false
                            );
    // parse for IsAnyRecordMatchCondition (for quantified subquery comparison)
    procedure ParseForIsAnyRecordMatchCondition(
                                           Cursor: TABSCursor;
                                           const FieldName: string;
                                           const Operator: TABSDataOperator;
                                           const Value: TABSVariant
                                           );
    // Parse For Boolean Expression (Filter, Where-Clause)
    procedure ParseForBooleanExpression(
                                        Dataset: TDataset;
                                        Lexer: TABSLexer
                                        //CaseInsensitive: boolean = true;
                                        //PartialKey: boolean = false
                                       );
    // Parse ValueExpression
    procedure ParseForValueExpression(
                                        Dataset: TDataset;
                                        Lexer: TABSLexer
                                        //CaseInsensitive: boolean = true;
                                        //PartialKey: boolean = false
                                       );
    // default value: const or function
    function  ParseForDefaultValueExpression(DefaultValue: string): Boolean;
    // Parse RowSubqueryExpression
    procedure ParseForRowSubqueryExpression(
                                        Dataset: TDataset;
                                        Lexer: TABSLexer
                                       );
    // Return Variant
    function GetValue(TrueFalseNullLogic: boolean = true): TABSVariant;
    // Get Boolean result
    function GetResult: Boolean;
    // calc subquery field values again
    procedure ResetSubqueryFieldValues;
    // Return subquery field value
    function GetSubqueryFieldValue(FieldNo: Integer): TABSVariant;


    // is expression contains aggregated function
    function IsAggregated: Boolean;
    function IsAggregatedCountAll: Boolean;
    // Init for aggregated functions
    procedure Init;
    // Accumulate for group functions
    procedure Accumulate;
    procedure SetCountAll(RecordCount: Integer);
    // process assign AO
    procedure AssignAO(AO, ParentQueryAO: TABSAO; ParentCursor: TABSCursor);
    // process assign Cursor and its RecordBuffer
    procedure AssignCursor(Cursor: TABSCursor; ParentQueryAO: TABSAO; ParentCursor: TABSCursor);
    // process assign New Cursor Buffer
    procedure AssignCursorBuffer(Buffer: TABSRecordBuffer; ApplyToParentQueryFields: Boolean);
    // return Type of Data
    function GetDataType: TABSAdvancedFieldType;
    // return Size of Data (for strings and arrays)
    function GetDataSize: Integer;
    // return Size of Data (for strings and arrays)
    function GetPrecision: Integer;
    // is expression contains no nodes
    function IsEmpty: Boolean;
    // is expression a Field (for join)
    function IsField: Boolean;
    // is expression a fields combination
    function IsFieldsExpression: Boolean;
    // Move AndNodes To RootNode
    procedure MoveAndNodesToRoot;
    // Field Name, Table Name
    procedure GetFieldInfo(var TableName: string; var FieldName: string);
    // makes filter string from related parts and sets it to AO
    procedure ApplyFilterParts(AO: TABSAO; ParentQueryAO: TABSAO;
                ParentCursor: TABSCursor;
                AllowRecurse: Boolean = True; ForHavingClause: Boolean = False);
    // replace pseudonyms to original names (f1 -> table1.field1)
    procedure ReplacePseudonyms(SelectList: array of TABSSelectListItem);
    // makes join field lists
    function ExtractJoinConditions(
                                    AO1, AO2: TABSAO;
                                    FieldList1, FieldList2: TABSFields
                                  ): Integer;
    // create conditions for index scan
    procedure TryCreateIndexScanConditionsFromNode(
                             Node:           TABSExprNode;
                             IndexDefs:      TABSIndexDefs;
                             ScanConditions: TABSScanSearchConditionArray;
                             SessionID: TABSSessionID);
    // remove extracted nodes (they included in list, but list contains more)
    procedure RemoveExtractedNodes(ScanConditions: TABSScanSearchConditionArray;
                                   ScanConditionNo, ScanEndConditionNo: Integer);
    // create conditions for index scan
    procedure CreateIndexScanConditions(
                             IndexDefs:               TABSIndexDefs;
                             ScanConditions:          TABSScanSearchConditionArray;
                             SessionID:               TABSSessionID);
    // try to add conditions on another index fields
    function ExtendMultiFieldIndexConditionFromNode(Node: TABSExprNode;
       ScanCondition: TABSScanSearchCondition; IndexDef: TABSIndexDef): Boolean;
    // try to add conditions on another index fields
    function ExtendMultiFieldIndexCondition(ScanCondition: TABSScanSearchCondition;
                                            IndexDef: TABSIndexDef): Boolean;
    // add extracted node
    procedure AddNode(Node: TABSExprNode);
    // add and move nodes from another expression: self => (self AND Expr2)
    procedure AddAndMoveNodesFromExpression(Expr: TABSExpression);
    // check if agregate expr, const or only grouped fields are used
    function CanBeUsedInGroupBy(GroupByFields: string): Boolean;
   public
    property InMemory: Boolean read FInMemory write FInMemory;
  end;




  TABSExprNode = class(TObject)
   private
    Children: TList;                  // Children nodes
    Operator: TABSDataOperator;      // '<',  '>', AND, NOT, ...
    Value:  TABSVariant;
    FCaseInsensitive: boolean;
    FPartialKey: boolean;
   private
    // converts some WideStrings to Strings
    procedure PatchWideStrings; virtual;
   public
    // constructors
    constructor Create(
                       CaseInsensitive: boolean = true;
                       PartialKey: boolean = false
                      ); overload;
    constructor Create(
                       Op: TABSDataOperator;
                       CaseInsensitive: boolean = true;
                       PartialKey: boolean = false
                      ); overload;
    constructor Create(
                       Op: TABSDataOperator;
                       Node: TABSExprNode;
                       CaseInsensitive: boolean = true;
                       PartialKey: boolean = false
                      ); overload;
    constructor Create(
                       Op: TABSDataOperator;
                       Node1, Node2: TABSExprNode;
                       CaseInsensitive: boolean = true;
                       PartialKey: boolean = false
                       ); overload;
    constructor Create(
                       Op: TABSDataOperator;
                       Node1, Node2, Node3: TABSExprNode;
                       CaseInsensitive: boolean = true;
                       PartialKey: boolean = false
                       ); overload;
    // destroys
    destructor Destroy; override;
    // return Value
    function GetDataValue: TABSVariant;  virtual; abstract;
    // calc subquery field values again
    procedure ResetSubqueryFieldValues; virtual;
    // Return subquery field value
    function GetSubqueryFieldValue(FieldNo: Integer): TABSVariant; virtual;
    // is expression contains aggregated function
    function IsAggregated: Boolean; virtual;
    function IsAggregatedCountAll: Boolean; virtual;
    // Init for group function
    procedure Init; virtual;
    // Accumulate for group functions
    procedure Accumulate; virtual;
    procedure SetCountAll(RecordCount: Integer); virtual;

    // process assign AO
    procedure AssignAO(AO, ParentQueryAO: TABSAO; ParentCursor: TABSCursor); virtual;
    // process assign Cursor
    procedure AssignCursor(Cursor: TABSCursor; ParentQueryAO: TABSAO; ParentCursor: TABSCursor); virtual;
    // process assign New Cursor Buffer
    procedure AssignCursorBuffer(Buffer: TABSRecordBuffer; ApplyToParentQueryFields: Boolean); virtual;

    // return Type of Data
    function GetDataType: TABSAdvancedFieldType; virtual;
    // return Size of Data (for strings and arrays)
    function GetDataSize: Integer; virtual;
    // return Data Precision
    function GetPrecision: Integer; virtual;

    // can be used by the AO?
    function CanBeAssigned(AO, ParentQueryAO: TABSAO; ParentCursor: TABSCursor; ForHavingClause: Boolean = False): Boolean; virtual;

    // node is field?
    function CanUseNodeAsField(Node: TABSExprNode): Boolean;
    // node is const or correlated field fomr parent query?
    function CanUseNodeAsConst(Node: TABSExprNode): Boolean;
    // can use index for comparison?
    function CanUseIndex(IndexColumnNo: Integer; ConditionSearchOperator: TABSSearchCondition; IndexDef: TABSIndexDef; SessionID: TABSSessionID = INVALID_SESSION_ID): Boolean; virtual;
  private
    // fill scan condition props
    procedure FillScanCondition(Value:         TABSVariant;
                                Cursor:        TABSCursor;
                                IndexColumnNo: Integer;
                                FieldNo:       Integer;
                                IndexDef:      TABSIndexDef;
                                Expression:    TABSExpression;
                                ScanCondition: TABSScanSearchCondition);

  public
    // add index scan condition
    procedure AddAsIndexScanCondition(
                   ScanConditions: TABSScanSearchConditionArray;
                   IndexDef:       TABSIndexDef;
                   Expression:  TABSExpression); virtual;
    // add checks to multi-field index scan condition
    procedure ExtendIndexScanCondition(
                   ScanCondition: TABSScanSearchCondition;
                   IndexDef:      TABSIndexDef;
                   Expression:    TABSExpression); virtual;

    // is node a join condition?
    function IsJoinCondition(AO1, AO2: TABSAO): Boolean; virtual;

    // replace pseudonyms to original names (f1 -> table1.field1)
    procedure ReplacePseudonyms(SelectList: array of TABSSelectListItem); virtual;

    // check if agregate expr, const or only grouped fields are used
    function CanBeUsedInGroupBy(GroupByFields: string): Boolean; virtual;
  end;

  TABSExprNodeConst = class(TABSExprNode)
   public
    // return Value
    function GetDataValue: TABSVariant; override;
    // return Type of Data
    function GetDataType: TABSAdvancedFieldType; override;
    // return Size of Data (for strings and arrays)
    function GetDataSize: Integer; override;

    function IsLikePatternEqualToPartialComparePattern: Boolean;
    procedure TruncateForPartialCompare;

    // can be used by the AO?
    function CanBeAssigned(AO, ParentQueryAO: TABSAO; ParentCursor: TABSCursor; ForHavingClause: Boolean = False): Boolean; override;
    function IsUpperCase: Boolean;
    function IsLowerCase: Boolean;
  end;


  TABSExprNodeField = class(TABSExprNode)
   private
    LCursor:              TABSCursor; // Cursor
    LAO:                  TABSAO;
    LRecordBuffer:        TAbsPByte;
    FTableName:           string;  // table name - Table1 (Table1.Field1)
    FFieldName:           string;  // field name - Field1
    FFieldNameInAO:       string;  // field name - could be like Field1_1
    FFieldNo:             Integer;
    FFieldOffsetInBuffer: Integer;
    FFieldType:           TABSAdvancedFieldType;
    FBaseFieldType:       TABSBaseFieldType;
    FFieldSize:           Integer;
    FFieldPrecision:      Integer;
    FGetFromParent:       Boolean;
    FScanOnlyVisibleFieldsInAO: Boolean;
   public
    constructor Create(Cursor: TABSCursor; FieldName: string; TableName: string = ''); overload;
    // Return Data Value
    function GetDataValue: TABSVariant;  override;
    // try to assign AO
    function TryAssignAO(AOToAssign: TABSAO): Boolean;
    // process assign AO
    procedure AssignAO(AO, ParentQueryAO: TABSAO; ParentCursor: TABSCursor); override;
    // find field No in cursor
    function FindFieldInCursor(Cursor: TABSCursor; var FieldNo: Integer): Boolean;
    // process assign Cursor and its buffer
    procedure AssignCursor(Cursor: TABSCursor; ParentQueryAO: TABSAO; ParentCursor: TABSCursor); override;
    // process assign New Cursor Buffer
    procedure AssignCursorBuffer(Buffer: TABSRecordBuffer; ApplyToParentQueryFields: Boolean); override;
    // return Type of Data
    function GetDataType: TABSAdvancedFieldType; override;
    // return Data Size
    function GetDataSize: Integer;  override;

    // can be used by the AO?
    function CanBeAssigned(AO, ParentQueryAO: TABSAO;
                           ParentCursor: TABSCursor; ForHavingClause: Boolean = False): Boolean; override;
    // fills Field Item
    procedure FillItem(var Item: TABSSelectListItem);
    // replace pseudonyms to original names (f1 -> table1.field1)
    procedure ReplacePseudonyms(SelectList: array of TABSSelectListItem); override;
    // check if agregate expr, const or only grouped fields are used
    function CanBeUsedInGroupBy(GroupByFields: string): Boolean; override;

   public
    property TableName: string read FTableName;
    property FieldName: string read FFieldName;
    property FieldNo: Integer read FFieldNo;
    property FieldSize: Integer read FFieldSize;
    property BaseFieldType: TABSBaseFieldType read FBaseFieldType;
  end;

  TABSExprNodeComparison = class(TABSExprNode)
   private
    F3ValueLogic: Boolean; // 3 Value Logic (TRUE, FALSE, NULL)
    FQuantifier: TABSQuantifier;
   private
    procedure PatchWideStrings; override;
    // x > y
    function GetDataValueByCompare: TABSVariant;
    // x > all (select ...)
    function GetDataValueByQuantifiedCompare: TABSVariant;
   public
    // Constructor
    constructor Create(
                       Op: TABSDataOperator;
                       Node1, Node2: TABSExprNode;
                       TrueFalseNullLogic: boolean = true;
                       CaseInsensitive: boolean = true;
                       PartialKey: boolean = false
                       ); overload;
    // Constructor
    constructor Create(
                       Op: TABSDataOperator;
                       Quantifier: TABSQuantifier;
                       Node1, Node2: TABSExprNode;
                       TrueFalseNullLogic: boolean = true;
                       CaseInsensitive: boolean = true;
                       PartialKey: boolean = false
                       ); overload;
    // Return Data Value
    function GetDataValue: TABSVariant;  override;
    procedure GetFieldAndConstNodes(var FieldNode: TABSExprNodeField; var ConstNode: TABSExprNodeConst);
    // can use index for comparison?
    function CanUseIndex(IndexColumnNo: Integer; ConditionSearchOperator: TABSSearchCondition; IndexDef: TABSIndexDef; SessionID: TABSSessionID = INVALID_SESSION_ID): Boolean; override;
    // add index scan condition
    procedure AddAsIndexScanCondition(
                   ScanConditions: TABSScanSearchConditionArray;
                   IndexDef:       TABSIndexDef;
                   Expression:  TABSExpression); override;

    function GetFieldNode(Node: TABSExprNode): TAbsExprNodeField;

    // add checks to multi-field index scan condition
    procedure ExtendIndexScanCondition(
                   ScanCondition: TABSScanSearchCondition;
                   IndexDef:      TABSIndexDef;
                   Expression:    TABSExpression); override;
    // is node a join condition?
    function IsJoinCondition(AO1, AO2: TABSAO): Boolean; override;
  end;


  TABSExprNodeBoolean = class(TABSExprNode)
   private
    TempVal: TABSVariant;
    EscapeStr: string;
   private
    // process a Like b
    procedure Like;
    // process IN (SELECT ...)
    procedure bInSubquery;
    // process IN (...)
    procedure bIn;
    // process A BETWEEN B AND C
    procedure Between;
    // process EXISTS
    procedure Exists;
   public
    constructor Create(
                       CaseInsensitive: boolean = true;
                       PartialKey: boolean = false
                      ); overload;
    constructor Create(
                       Op: TABSDataOperator;
                       CaseInsensitive: boolean = true;
                       PartialKey: boolean = false;
                       aEscapeStr: string=''
                      ); overload;
    constructor Create(
                       Op: TABSDataOperator;
                       Node: TABSExprNode;
                       CaseInsensitive: boolean = true;
                       PartialKey: boolean = false
                      ); overload;
    constructor Create(
                       Op: TABSDataOperator;
                       Node1, Node2: TABSExprNode;
                       CaseInsensitive: boolean = true;
                       PartialKey: boolean = false
                       ); overload;
    constructor Create(
                       Op: TABSDataOperator;
                       Node1, Node2, Node3: TABSExprNode;
                       CaseInsensitive: boolean = true;
                       PartialKey: boolean = false
                       ); overload;
    destructor Destroy; override;
    // Return Data Value
    function GetDataValue: TABSVariant;  override;
    // can use index for comparison?
    function CanUseIndex(ColumnNo: Integer; ConditionSearchOperator: TABSSearchCondition;
     IndexDef: TABSIndexDef; SessionID: TABSSessionID = INVALID_SESSION_ID): Boolean; override;
    // add index scan condition
    procedure AddAsIndexScanCondition(
                   ScanConditions: TABSScanSearchConditionArray;
                   IndexDef:       TABSIndexDef;
                   Expression:  TABSExpression); override;
    // add checks to multi-field index scan condition
    procedure ExtendIndexScanCondition(
                   ScanCondition: TABSScanSearchCondition;
                   IndexDef:      TABSIndexDef;
                   Expression:    TABSExpression); override;
  end;


  TABSExprNodeArithmetic = class(TABSExprNode)
   protected
    // SUB operation
    procedure SubData;
    // ADD operation
    procedure AddData;
    // MUL operation
    procedure MulData;
    // DIV operation
    procedure DivData;
    // ABS operation
    procedure GetABSValue;
    // ACOS operation
    procedure GetACOSValue;
    // ASIN operation
    procedure GetASINValue;
    // ATAN operation
    procedure GetATANValue;
    // CEIL operation
    procedure GetCEILValue;
    // COS operation
    procedure GetCOSValue;
    // EXP operation
    procedure GetEXPValue;
    // FLOOR operation
    procedure GetFLOORValue;
    // LOG operation
    procedure GetLOGValue;
    // POWER operation
    procedure GetPOWERValue;
    // RAND operation
    procedure GetRANDValue;
    // ROUND operation
    procedure GetROUNDValue;
    // SIGN operation
    procedure GetSIGNValue;
    // SIN operation
    procedure GetSINValue;
    // SQR operation
    procedure GetSQRValue;
    // SQRT operation
    procedure GetSQRTValue;
   public
    // Return Data Value
    function GetDataValue: TABSVariant;  override;
    // get value data type
    function GetDataType: TABSAdvancedFieldType; override;

  end;


  TABSExprNodeAggregated = class(TABSExprNode)
   private
    count: integer;
    distinct: Boolean;
    AvgSum: TABSVariant;
   public
    // Constructors
    constructor Create(Op: TABSDataOperator); overload;
    constructor Create(Op: TABSDataOperator;
                       distinct: boolean;
                       Node: TABSExprNode); overload;
    // Destructor
    destructor Destroy; override;
    // Return Data Value
    function GetDataValue: TABSVariant;  override;
    // return Type of Data
    function GetDataType: TABSAdvancedFieldType; override;
    // return Size of Data (for strings and arrays)
    function GetDataSize: Integer; override;
    // is expression contains aggregated function
    function IsAggregated: Boolean; override;
    function IsAggregatedCountAll: Boolean; override;
    function CanBeAssigned(AO, ParentQueryAO: TABSAO;
                           ParentCursor: TABSCursor; ForHavingClause: Boolean = False): Boolean; override;
    // Init for aggregated functions
    procedure Init; override;
    // Accumulate for group functions
    procedure Accumulate; override;
    procedure SetCountAll(RecordCount: Integer); override;
    // check if agregate expr, const or only grouped fields are used
    function CanBeUsedInGroupBy(GroupByFields: string): Boolean; override;
  end;


  TABSExprNodeCast = class(TABSExprNode)
  private
    FCastType:      TABSAdvancedFieldType;  // Type for convertation
    LCastSizeNode:  TABSExprNode;
   public
    constructor Create(Node:          TABSExprNode;
                       CastType:      TABSAdvancedFieldType;
                       CastSizeNode:  TABSExprNode = nil); overload;
    // Return Data Value
    function GetDataValue: TABSVariant;  override;
    // return Type of Data
    function GetDataType: TABSAdvancedFieldType; override;
    // return Size of Data (for strings and arrays)
    function GetDataSize: Integer; override;
  end;


  TABSExprNodeCase = class(TABSExprNode)
   private
    FWhenExpressions: TList;
    FResultExpressions: TList;
    FElseResultExpression: TABSExprNode;
    FInputExpression: TABSExprNode;
    FCoalesceNodes: TABSExprNodeArray;
    FIsCoalesceExpression: Boolean;
   public
    // Constructors
    procedure CreateInit;
    constructor CreateSimpleCase(InputExpression: TABSExprNode);
    constructor CreateSearchedCase;
    constructor CreateCoalesce(Nodes: TABSExprNodeArray);
    // Destructor
    destructor Destroy; override;

    // Return Data Value
    function GetDataValue: TABSVariant;  override;
    // return Type of Data
    function GetDataType: TABSAdvancedFieldType; override;
    // return Size of Data (for strings and arrays)
    function GetDataSize: Integer; override;

    procedure AddWhenExpression(Node: TABSExprNode);
    procedure AddResultExpression(Node: TABSExprNode);
    procedure SetElseResultExpression(Node: TABSExprNode);
  end;

  TABSExprNodeBlob = class(TABSExprNode)
   private
    procedure MimeToBin;
   public
    function GetDataValue: TABSVariant;  override;
    function GetDataType: TABSAdvancedFieldType; override;
    function GetDataSize: Integer; override;
  end;

  
  TABSExprNodeStringFunction = class(TABSExprNode)
   protected
    function InitStringValue: Boolean;
   protected
    procedure Concat;
    procedure Upper;
    procedure Lower;
    procedure Trim;
    procedure LTrim;
    procedure RTrim;
    procedure Length;
    procedure Pos;
    procedure Substring;
   public
    // Return Data Value
    function GetDataValue: TABSVariant;  override;
    // return Type of Data
    function GetDataType: TABSAdvancedFieldType; override;
    // return Size of Data (for strings and arrays)
    function GetDataSize: Integer; override;
  end;


  TABSExprNodeSystem = class(TABSExprNode)
   private
    FTableName:   string;
    FColumnName:  string;
    FInMemory:    Boolean;
    FTable:       TDataSet;
    FFieldType:   TABSAdvancedFieldType;
    FRowNumValue: integer;
   private
    procedure LastAutoInc;
    procedure RowNum;
   public
    // creates
    constructor Create(Operator: TABSDataOperator; SessionName, DatabaseName, TableName, ColumnName: string; InMemory: Boolean); overload;
    // Destructor
    destructor Destroy; override;
    // Return Data Value
    function GetDataValue: TABSVariant;  override;
    // return Type of Data
    function GetDataType: TABSAdvancedFieldType; override;
    // return Size of Data (for strings and arrays)
    function GetDataSize: Integer; override;
  end;


  TABSExprNodeDateFunction = class(TABSExprNode)
   private
    DateFormater: TDateFormater;
    ExtractDateTimeField: TABSDateTimeExtractField;

    // ToDate
    procedure ToDate;
    // ToString
    procedure ToString;
    // Extract
    procedure Extract;
   public
    // Constructor
    constructor Create(Op: TABSDataOperator;
                       Node: TABSExprNode;
                       FormatStr: string); overload;
    constructor Create(Op: TABSDataOperator;
                       ExtractField: TABSDateTimeExtractField;
                       ExtractSource: TABSExprNode); overload;

    // Destructor
    destructor Destroy; override;
    // Return Data Value
    function GetDataValue: TABSVariant;  override;
    // return Type of Data
    function GetDataType: TABSAdvancedFieldType; override;
    // return Size of Data (for strings and arrays)
    function GetDataSize: Integer; override;
  end;


  TCheckResultsMethod = function: Boolean of object;
  TABSExprNodeSubquery = class(TABSExprNode)
   private
    FMustHaveOneField: Boolean;
    FMustHaveOneRow: Boolean;
    FValue: TABSVariant;
    FFieldValues: array of TABSVariant;
    LDataset: TDataset;
    LLexer: TABSLexer;
    LAO: TABSAO;
    LCursor: TABSCursor;
    LCursorRecordBuffer: TABSRecordBuffer;
    FSubquery: TObject;// TABSSQLUnion
    FCommandNo: Integer;
    FTokenNo: Integer;
    LValueToCheck: TABSVariant;
    FPriorValueToCheck: TABSVariant;
    FPriorResult: Boolean;
    FPriorResultInitalized: Boolean;
    FCompareOperator: TABSDataOperator;
    FCompareQuantifier: TABSQuantifier;
    FCorrelated: Boolean;

    function CheckGetDataValue: Boolean;
    function CheckGetFieldValues: Boolean;
    function CheckExists: Boolean;
    function CheckIsValueIn: Boolean;
    function CheckQuantifiedCompare: Boolean;
    function RunQueryAndCheckResults(CheckMethod: TCheckResultsMethod): Boolean;
   public
    // Constructor
    constructor Create(MustHaveOneField: Boolean; MustHaveOneRow: Boolean;
                       Dataset: TDataset; Lexer: TABSLexer);
    destructor Destroy; override;
    // process assign cursor
    procedure AssignCursor(Cursor: TABSCursor; ParentQueryAO: TABSAO; ParentCursor: TABSCursor); override;
    // process assign New Cursor Buffer
    procedure AssignCursorBuffer(Buffer: TABSRecordBuffer; ApplyToParentQueryFields: Boolean); override;
    // process assign AO
    procedure AssignAO(AO, ParentQueryAO: TABSAO; ParentCursor: TABSCursor); override;
    function CanBeAssigned(AO, ParentQueryAO: TABSAO; ParentCursor: TABSCursor; ForHavingClause: Boolean = False): Boolean; override;
    // Return Data Value
    function GetDataValue: TABSVariant;  override;
    // calc subquery field values again
    procedure ResetSubqueryFieldValues; override;
    // Return subquery field value
    function GetSubqueryFieldValue(FieldNo: Integer): TABSVariant; override;
    // return Type of Data
    function GetDataType: TABSAdvancedFieldType; override;
    // return Data Size
    function GetDataSize: Integer;  override;
    // EXISTS subquery
    function Exists: Boolean;
    // IN subquery
    function IsValueIn(Value: TABSVariant): Boolean;
    // > ALL subquery
    function QuantifiedCompare(Operator: TABSDataOperator;
                       Quantifier: TABSQuantifier; Value: TABSVariant): Boolean;
  end;




// get operator name
function GetOperatorName(op: TABSDataOperator): string;

// Return FieldType
function GetFieldType(const TypeName: string): TABSAdvancedFieldType;

procedure FillFieldNames(List: TStringList; const Names: string);

// "<" => ">", "=" => "="
function ReverseComparisonOperator(CompOp: TABSDataOperator): TABSDataOperator;

// "<" => ">=", "=" => "<>"
function InvertComparisonOperator(CompOp: TABSDataOperator): TABSDataOperator;

function IsNonConstantExpression(Value: TABSVariant): Boolean;

implementation

uses ABSMain, ABSCompression, ABSSQLProcessor, ABSLocalEngine, ABSDecUtil,
     Math, ABSStrUtils, ABSDecimalRounding;

//------------------------------------------------------------------------------
// get operator name
//------------------------------------------------------------------------------
function GetOperatorName(op: TABSDataOperator): string;
begin
  case op of
    doNOT:          Result := 'NOT';
    doAND:          Result := 'AND';
    doOR:           Result := 'OR';
    doEQ:           Result := '=';
    doNE:           Result := '<>';
    doLT:           Result := '<';
    doGT:           Result := '>';
    doLE:           Result := '<=';
    doGE:           Result := '>=';
    doLIKE:         Result := 'LIKE';
    doNOTLIKE:      Result := 'NOT LIKE';
    doISNULL:       Result := 'IS NULL';
    doISNOTNULL:    Result := 'IS NOT NULL';
    doADD:          Result := '+';
    doSUB:          Result := '-';
    doMUL:          Result := '*';
    doDIV:          Result := '/';
    doCONCAT:       Result := '||';
    doUPPER:        Result := 'UPPER';
    doLOWER:        Result := 'LOWER';
    doTRIM:         Result := 'TRIM';
    doLTRIM:        Result := 'LTRIM';
    doRTRIM:        Result := 'RTRIM';
    doSUM:          Result := 'SUM';
    doAVG:          Result := 'AVG';
    doMIN:          Result := 'MIN';
    doMAX:          Result := 'MAX';
    doCOUNT:        Result := 'COUNT';
    doCOUNTALL:     Result := 'COUNT(*)';
    else            Result:='unknown';
  end;
end;//GetOperatorName


//------------------------------------------------------------------------------
// Return FieldType
//------------------------------------------------------------------------------
function GetFieldType(const TypeName: string): TABSAdvancedFieldType;
var
  i: Integer;
  s: string;
begin
  Result := aftUnknown;
  s := UpperCase(TypeName);
  for i:=Low(SQLFieldTypes) to High(SQLFieldTypes) do
    if SQLFieldTypes[i].SqlName = s then
      begin
        Result := SQLFieldTypes[i].AdvancedFieldType;
        break;
      end;
end;//GetFieldType



////////////////////////////////////////////////////////////////////////////////
//
// TABSExpression
//
////////////////////////////////////////////////////////////////////////////////


//------------------------------------------------------------------------------
// Constructor
//------------------------------------------------------------------------------
constructor TABSExpression.Create;
begin
  FRootExprNode := nil;
  LLex := nil;
  FCaseInsensitive := False;
  FPartialKey := False;
  F3ValueLogic := True;
end;//Create


//------------------------------------------------------------------------------
// Constructor
//------------------------------------------------------------------------------
constructor TABSExpression.Create(RootNode: TABSExprNode);
begin
  Create;
  FRootExprNode := RootNode;
end;//Create


//------------------------------------------------------------------------------
// Constructor
//------------------------------------------------------------------------------
//constructor TABSExpression.Create(Lexer: TABSLexer);
//begin
//  Create;
//  LLex := Lexer;
//end;//Create



//------------------------------------------------------------------------------
// Destructor
//------------------------------------------------------------------------------
destructor TABSExpression.Destroy;
begin
  Clear;
  inherited;
end;//Destroy


//------------------------------------------------------------------------------
// Clear all Variables
//------------------------------------------------------------------------------
procedure TABSExpression.Clear;
begin
  // free nodes tree
  if (FRootExprNode <> nil) then
    begin
      FRootExprNode.Free;
      FRootExprNode := nil;
    end;
end;//Clear


//------------------------------------------------------------------------------
// Parsing for Locate
//------------------------------------------------------------------------------
procedure TABSExpression.ParseForLocate(
                                          Cursor: TABSCursor;
                                          FieldNames: string; // name1;name2
                                          KeyValues: Variant; //
                                          CaseInsensitive: boolean = true;
                                          PartialKey: boolean = false
                                       );
var
  i, ArrLen: Integer;
  FieldNamesList: TStringList;
  //Arr: Pointer;
  LeftNField: TABSExprNodeField;
  RightNConst: TABSExprNodeConst;
  EqNode: TABSExprNodeComparison;
  IsArray: Boolean;
  //AndNode: TABSExprNodeBoolean;
  //val: TABSVariant;

  procedure AssignConstValue(const Value: Variant);
  begin
    if (LeftNField.BaseFieldType in [bftDate, bftTime, bftDateTime]) and
       (VarType(Value) in [varInteger, varDouble]) then
      RightNConst.Value.AsVariant := TDateTime(Value)
    else
      RightNConst.Value.AsVariant := Value;
  end;

begin
  Clear;
  LCursor := Cursor;
  F3ValueLogic := False;

  FieldNamesList := TStringList.Create;
  try
    FillFieldNames(FieldNamesList, FieldNames);

    // Determinate KeyValues count (can be array)
    if  (VarType(KeyValues) and varArray ) <> 0 then
      begin
        IsArray := true;
        ArrLen := VarArrayHighBound(KeyValues,1) - VarArrayLowBound(KeyValues,1) + 1
      end
    else
      begin
        IsArray := False;
        ArrLen := 1;
      end;

    if (FieldNamesList.Count <> ArrLen) then
      raise EABSException.Create(30112, ErrorGNotEqualCountsOfFieldNamesAndKeyValues,
                                      [FieldNamesList.Count, ArrLen]);

    if (not IsArray) then
      begin
        // Field
        LeftNField := TABSExprNodeField.Create(LCursor, FieldNamesList[0]);
        // Const
        RightNConst := TABSExprNodeConst.Create;
        AssignConstValue(KeyValues);
        // RootNode
        FRootExprNode := TABSExprNodeComparison.Create(doEQ, LeftNField,
                                      RightNConst, F3ValueLogic, CaseInsensitive, PartialKey);
      end
    else
      begin
        FRootExprNode := TABSExprNodeBoolean.Create(doAND);
        for i:=0 to ArrLen-1 do
          begin
            // Field
            LeftNField := TABSExprNodeField.Create(LCursor, FieldNamesList[i]);
            // Check FieldExists
            // ...
            // Const
            RightNConst := TABSExprNodeConst.Create;
            AssignConstValue(VarArrayGet(KeyValues, i));

            // EqNode
            EqNode := TABSExprNodeComparison.Create(doEQ, LeftNField, RightNConst,
                                       F3ValueLogic, CaseInsensitive, PartialKey);
            // Add to RootNode
            FRootExprNode.Children.Add(EqNode);
          end;
      end;
  finally
    FieldNamesList.Free;
  end;

  if LCursor <> nil then
    FRootExprNode.PatchWideStrings;

end;//ParseForLocate


//------------------------------------------------------------------------------
// Parsing for Filter
//------------------------------------------------------------------------------
procedure TABSExpression.ParseForFilter(Cursor: TABSCursor; Filter: string;
  CaseInsensitive, PartialKey: boolean);
var
  Lexer: TABSLexer;
begin
  LCursor := Cursor;
  FCaseInsensitive := CaseInsensitive;
  FPartialKey := PartialKey;

  Lexer := TABSLexer.Create(Filter);
  try
    if (Lexer.NumCommands = 0) then
       raise EABSException.Create(30119, ErrorGBlankSqlCommand);
    Lexer.GetNextCommand;
    ParseForBooleanExpression(nil, Lexer);
  finally
    Lexer.Free;
  end;
end;//ParseForFilter


//------------------------------------------------------------------------------
// parse for IsAnyRecordMatchCondition (for quantified subquery comparison)
//------------------------------------------------------------------------------
procedure TABSExpression.ParseForIsAnyRecordMatchCondition(
                                           Cursor: TABSCursor;
                                           const FieldName: string;
                                           const Operator: TABSDataOperator;
                                           const Value: TABSVariant
                                           );
var
  LeftNField: TABSExprNodeField;
  RightNConst: TABSExprNodeConst;
begin
  Clear;
  LCursor := Cursor;
  F3ValueLogic := False;
  // Field
  LeftNField := TABSExprNodeField.Create(LCursor, FieldName);
  // Const
  RightNConst := TABSExprNodeConst.Create;
  RightNConst.Value.Assign(Value);
  // RootNode
  FRootExprNode := TABSExprNodeComparison.Create(Operator, LeftNField,
                   RightNConst, F3ValueLogic, False, False);
  if LCursor <> nil then
    FRootExprNode.PatchWideStrings;
end;// ParseForIsAnyRecordMatchCondition


//------------------------------------------------------------------------------
// Parse For Boolean Expression (Filter, Where-Clause)
//------------------------------------------------------------------------------
procedure TABSExpression.ParseForBooleanExpression(
                                        Dataset: TDataset;
                                        Lexer: TABSLexer
                                        //CaseInsensitive: boolean = true;
                                        //PartialKey: boolean = false
                                                   );
begin
  Clear;
  LDataset := Dataset;
  LLex := Lexer;

  if (not LLex.GetCurrentToken(Token)) then
    raise EABSException.Create(30118, ErrorGUnexpectedEndOfCommand,
                                              [Token.LineNum, Token.ColumnNum]);
  // Parse...
  FRootExprNode := ParseSearchCondition;

  if (FRootExprNode = nil) then
    raise EABSException.Create(30066, ErrorGBooleanExpressionExpected,
                               [Token.Text, Token.LineNum, Token.ColumnNum]);
  MoveAndNodesToRoot;

  if LCursor <> nil then
    FRootExprNode.PatchWideStrings;
end;//ParseForBooleanExpression


//------------------------------------------------------------------------------
// Parse ValueExpression
//------------------------------------------------------------------------------
procedure TABSExpression.ParseForValueExpression(
                                        Dataset: TDataset;
                                        Lexer: TABSLexer
                                        //CaseInsensitive: boolean = true;
                                        //PartialKey: boolean = false
                                                 );
begin
 LLex := Lexer;
 LDataset := Dataset;
 if (Dataset <> nil) then begin
   FDatabaseName := TABSDataset(Dataset).DatabaseName;
   FSessionName := TABSDataset(Dataset).SessionName;
 end;
 //LCursor := Cursor;
 //FCaseInsensitive := CaseInsensitive;
 //FPartialKey := PartialKey;

 // get first token (for very beginning of the query) or current token
 if (not LLex.GetCurrentToken(Token)) then
  raise EABSException.Create(30136, ErrorGUnexpectedEndOfCommand, [Token.LineNum, Token.ColumnNum]);

 // parse
 FRootExprNode := ParseValueExpression;
end;//ParseForValueExpression


//------------------------------------------------------------------------------
// default value: const or function
//------------------------------------------------------------------------------
function TABSExpression.ParseForDefaultValueExpression(DefaultValue: string): Boolean;
var
  Lexer: TABSLexer;
  Token: TToken;
begin
  try
    Lexer := TABSLexer.Create(DefaultValue, nil, true);
    try
      if (Lexer.NumCommands = 0) then
         Result := False
      else
        begin
          Lexer.GetNextCommand;
          ParseForValueExpression(nil, Lexer);
          Result := (Lexer.NumCommands = 1) and (not Lexer.LookNextToken(Token));
        end;
    finally
      Lexer.Free;
    end;
  except
    Result := False;
  end;
end;// ParseForDefaultValueExpression


//------------------------------------------------------------------------------
// Parse RowSubqueryExpression
//------------------------------------------------------------------------------
procedure TABSExpression.ParseForRowSubqueryExpression(
                                    Dataset: TDataset;
                                    Lexer: TABSLexer);
begin
 LLex := Lexer;
 LDataset := Dataset;
 FDatabaseName := TABSDataset(Dataset).DatabaseName;
 FSessionName := TABSDataset(Dataset).SessionName;

 // get first token (for very beginning of the query) or current token
 if (not LLex.GetCurrentToken(Token)) then
  raise EABSException.Create(30497, ErrorGUnexpectedEndOfCommand, [Token.LineNum, Token.ColumnNum]);

 // parse
 FRootExprNode := ParseRowSubquery(False, False);
end;// ParseForRowSubqueryExpression


//------------------------------------------------------------------------------
// Return Variant
//------------------------------------------------------------------------------
function TABSExpression.GetValue(TrueFalseNullLogic: boolean): TABSVariant;
begin
  if FRootExprNode <> nil then
    begin
      Result := FRootExprNode.GetDataValue;
      if ( Result.IsNull and (not TrueFalseNullLogic) ) then
        Result.AsBoolean := False;
    end
  else
    Result := nil;
end;//GetValue


//------------------------------------------------------------------------------
// Get Boolean result
//------------------------------------------------------------------------------
function TABSExpression.GetResult: Boolean;
var
  Value: TABSVariant;
begin
  Value := GetValue(false);
  if Value = nil then
    raise EABSException.Create(30110, ErrorGValueIsNull);
  Result := Value.AsBoolean;
end;//GetResult


//------------------------------------------------------------------------------
// calc subquery field values again
//------------------------------------------------------------------------------
procedure TABSExpression.ResetSubqueryFieldValues;
begin
  if FRootExprNode <> nil then
    FRootExprNode.ResetSubqueryFieldValues;
end;// ResetSubqueryFieldValues


//------------------------------------------------------------------------------
// Return subquery field value
//------------------------------------------------------------------------------
function TABSExpression.GetSubqueryFieldValue(FieldNo: Integer): TABSVariant;
begin
  if FRootExprNode <> nil then
    Result := FRootExprNode.GetSubqueryFieldValue(FieldNo)
  else
    Result := nil;
end;// GetSubqueryFieldValue


//------------------------------------------------------------------------------
// is expression contains aggregated function
//------------------------------------------------------------------------------
function TABSExpression.IsAggregated: Boolean;
begin
 if FRootExprNode <> nil then
   Result := FRootExprNode.IsAggregated
 else
   Result := False;
end;//IsAggregated


//------------------------------------------------------------------------------
// is Count(*)
//------------------------------------------------------------------------------
function TABSExpression.IsAggregatedCountAll: Boolean;
begin
 if FRootExprNode <> nil then
   Result := FRootExprNode.IsAggregatedCountAll
 else
   Result := False;
end;// IsAggregatedCountAll


//------------------------------------------------------------------------------
// Init for aggregated functions
//------------------------------------------------------------------------------
procedure TABSExpression.Init;
begin
  if FRootExprNode <> nil then
    FRootExprNode.Init;
end;//Init


//------------------------------------------------------------------------------
// Accumulate for group functions
//------------------------------------------------------------------------------
procedure TABSExpression.Accumulate;
begin
  if FRootExprNode <> nil then
    FRootExprNode.Accumulate;
end;//Accumulate


//------------------------------------------------------------------------------
// SetCountAll
//------------------------------------------------------------------------------
procedure TABSExpression.SetCountAll(RecordCount: Integer);
begin
  if FRootExprNode <> nil then
    FRootExprNode.SetCountAll(RecordCount);
end;// SetCountAll


//------------------------------------------------------------------------------
// process assign AO
//------------------------------------------------------------------------------
procedure TABSExpression.AssignAO(AO, ParentQueryAO: TABSAO; ParentCursor: TABSCursor);
begin
  if (FRootExprNode <> nil) then
    begin
      FRootExprNode.AssignAO(AO, ParentQueryAO, ParentCursor);
      FRootExprNode.PatchWideStrings;
    end;
end;//AssignAO


//------------------------------------------------------------------------------
// process assign Cursor and its RecordBuffer
//------------------------------------------------------------------------------
procedure TABSExpression.AssignCursor(Cursor: TABSCursor; ParentQueryAO: TABSAO; ParentCursor: TABSCursor);
begin
  if (FRootExprNode <> nil) then
    begin
      FRootExprNode.AssignCursor(Cursor, ParentQueryAO, ParentCursor);
      FRootExprNode.PatchWideStrings;
    end;
end;//AssignCursor


//------------------------------------------------------------------------------
// process assign New Cursor Buffer
//------------------------------------------------------------------------------
procedure TABSExpression.AssignCursorBuffer(Buffer: TABSRecordBuffer; ApplyToParentQueryFields: Boolean);
begin
  if (FRootExprNode <> nil) then
    begin
      FRootExprNode.AssignCursorBuffer(Buffer, ApplyToParentQueryFields);
      FRootExprNode.PatchWideStrings;
    end;
end;//AssignCursorBuffer


//------------------------------------------------------------------------------
// return Expression DataSize
//------------------------------------------------------------------------------
function TABSExpression.GetDataSize: Integer;
begin
 if FRootExprNode <> nil then
   Result := FRootExprNode.getDataSize
 else
   Result := 0;
end;//getDataSize


//------------------------------------------------------------------------------
// return Size of Data (for strings and arrays)
//------------------------------------------------------------------------------
function TABSExpression.GetPrecision: Integer;
begin
 if FRootExprNode <> nil then
   Result := FRootExprNode.GetPrecision
 else
   Result := 0;
end;//GetPrecision


//------------------------------------------------------------------------------
// return Expression DataType
//------------------------------------------------------------------------------
function TABSExpression.GetDataType: TABSAdvancedFieldType;
begin
 if FRootExprNode <> nil then
   Result := FRootExprNode.getDataType
 else
   Result := aftUnknown;
end;//GetDataType


//------------------------------------------------------------------------------
// is expression contains no nodes
//------------------------------------------------------------------------------
function TABSExpression.IsEmpty: Boolean;
begin
  Result := (FRootExprNode = nil);
  if (not Result) then
    if (FRootExprNode is TABSExprNodeBoolean) then
      Result := (FRootExprNode.Children.Count = 0);
end;//IsEmpty


//------------------------------------------------------------------------------
// is expression a Field (for join)
//------------------------------------------------------------------------------
function TABSExpression.IsField: Boolean;
begin
  Result := False;
  if FRootExprNode <> nil then
    Result := FRootExprNode is TABSExprNodeField;
end;//IsField

//------------------------------------------------------------------------------
// is expression a combination of fields
//------------------------------------------------------------------------------
function TABSExpression.IsFieldsExpression: Boolean;
var
  I: Integer;
begin
  Result := False;
  if FRootExprNode <> nil then
  begin
    if (FRootExprNode is TABSExprNodeField) then
    begin
      Result := True;
      Exit;
    end;
    for I := 0 to FRootExprNode.Children.Count - 1 do
    begin
      if (TABSExprNode(FRootExprNode.Children[I]) is TABSExprNodeField) then
      begin
        Result := True;
        Exit;
      end;
    end;
  end;
end;



//------------------------------------------------------------------------------
// Move AndNodes To RootNode
//------------------------------------------------------------------------------
procedure TABSExpression.MoveAndNodesToRoot;
var
  i: Integer;
begin
  if (FRootExprNode.Operator = doAND) then
   begin
    i := 0;
    while i < FRootExprNode.Children.Count do
     begin
      if (TABSExprNode(FRootExprNode.Children[i]).Operator = doAND) then
       begin
        while TABSExprNode(FRootExprNode.Children[i]).Children.Count > 0 do
         begin
          FRootExprNode.Children.Add(TABSExprNode(FRootExprNode.Children[i]).Children[0]);
          TABSExprNode(FRootExprNode.Children[i]).Children.Delete(0);
         end;
        TABSExprNode(FRootExprNode.Children[i]).Free;
        FRootExprNode.Children.Delete(i);
        Continue;
       end;
      Inc(i);
     end;
   end;
end;//MoveAndNodesToRoot


//------------------------------------------------------------------------------
// Field Name, Table Name
//------------------------------------------------------------------------------
procedure TABSExpression.GetFieldInfo(var TableName, FieldName: string);
begin
  if (IsField) then
   begin
    TableName := TABSExprNodeField(FRootExprNode).FTableName;
    FieldName := TABSExprNodeField(FRootExprNode).FFieldName;
   end
  else
   raise EABSException.Create(30198, ErrorGNodeIsNotField);
end;// GetFieldInfo


//------------------------------------------------------------------------------
// makes filter from related parts and sets it to AO
//------------------------------------------------------------------------------
procedure TABSExpression.ApplyFilterParts(AO: TABSAO; ParentQueryAO: TABSAO;
                ParentCursor: TABSCursor;
                AllowRecurse: Boolean = True; ForHavingClause: Boolean = False);
var
  NodesToDelete: TList;
  Node: TABSExprNode;
  bRecurse: Boolean;
  i: Integer;
  FilterRootNode: TABSExprNode;
begin
  FilterRootNode := nil;
  bRecurse := AllowRecurse;
  // don't set filter to outer join childs
  if bRecurse and (AO is TABSAOJoin) then
    bRecurse := not TABSAOJoin(AO).OuterJoin;

  // try to apply to AO children
  if (bRecurse) then
   begin
    if (AO.FLeftAO <> nil) then
     ApplyFilterParts(AO.FLeftAO, ParentQueryAO, ParentCursor, AllowRecurse, ForHavingClause);
    if (AO.FRightAO <> nil) then
     ApplyFilterParts(AO.FRightAO, ParentQueryAO, ParentCursor, AllowRecurse, ForHavingClause);
   end;

  // traverse tree by factors: (...) AND (...) AND (...)
  if (FRootExprNode <> nil) then
   begin
     NodesToDelete := TList.Create;
     try
      // -> (...) AND (...) AND (...) ?
      if (FRootExprNode is TABSExprNodeBoolean) and
         (FRootExprNode.Operator = doAND) then
        for i:=0 to FRootExprNode.Children.Count-1 do
         begin
          Node := FRootExprNode.Children[i];
          // (...) can be used by the AO?
          if (Node.CanBeAssigned(AO, ParentQueryAO, ParentCursor, ForHavingClause)) then
           begin
            // no FilterRootNode
            if FilterRootNode = nil then
              FilterRootNode := Node
            else
            // FilterRootNode=AND ( ex: a=b AND c=d )
            if (FilterRootNode is TABSExprNodeBoolean) and
               (FilterRootNode.Operator = doAND) then
               FilterRootNode.Children.Add(Node)
            else
              // single FilterRootNode ( ex: a=b ) - converting to AND
              FilterRootNode := TABSExprNodeBoolean.Create(doAND,
                                                FilterRootNode, Node);
            NodesToDelete.Add(Node);
           end;
          // -> AND (...)
         end
      else
        // (...)
        begin
          if (FRootExprNode.CanBeAssigned(AO, ParentQueryAO, ParentCursor, ForHavingClause)) then
            begin
              FilterRootNode := FRootExprNode;
              FRootExprNode := nil;
            end;
        end;
      // delete used nodes
      for i:=0 to NodesToDelete.Count-1 do
       begin
        FRootExprNode.Children.Delete(
          FRootExprNode.Children.IndexOf(NodesToDelete[i]));
       end;
      // set extracted filter
      if FilterRootNode <> nil then
       AO.SetFilter(TABSExpression.Create(FilterRootNode));

      // no children?
      if (FRootExprNode <> nil) then
       if (FRootExprNode.Children.Count = 0) then
        begin
         FRootExprNode.Free;
         FRootExprNode := nil;
        end;
     finally
      NodesToDelete.Free;
     end;
   end;
end;// ApplyFilterParts


//------------------------------------------------------------------------------
// gets current token
//------------------------------------------------------------------------------
procedure TABSExpression.ReplacePseudonyms(SelectList: array of TABSSelectListItem);
begin
 if (FRootExprNode <> nil) then
    FRootExprNode.ReplacePseudonyms(SelectList);
end;//ReplacePseudonyms


//------------------------------------------------------------------------------
// makes join field lists
//------------------------------------------------------------------------------
function TABSExpression.ExtractJoinConditions(
                                    AO1, AO2: TABSAO;
                                    FieldList1, FieldList2: TABSFields
                                             ): Integer;
var
  NodesToDelete: TList;
  Node: TABSExprNode;
  Item1,Item2: TABSSelectListItem;
  i: Integer;
begin
  Result := 0;
  if FRootExprNode = nil then Exit;
  // If 'AND'
  if ((FRootExprNode is TABSExprNodeBoolean) and
      (FRootExprNode.Operator = doAND)) then
   begin
     NodesToDelete := TList.Create;
     try
      for i:=0 to FRootExprNode.Children.Count-1 do
       begin
        Node := FRootExprNode.Children[i];
        // (...) can be used by the AO?
        if (Node.IsJoinCondition(AO1,AO2)) then
         begin
          // store extracted filter
          TABSExprNodeField(Node.Children[0]).FillItem(Item1);
          TABSExprNodeField(Node.Children[1]).FillItem(Item2);
          FieldList1.Append(Item1);
          FieldList2.Append(Item2);
          Inc(Result);
          // will delete this node
          NodesToDelete.Add(Node);
         end;
        // -> AND (...)
        //Node := Node.NextFactor;
       end;
      // delete used nodes
      for i:=0 to NodesToDelete.Count-1 do
       begin
        FRootExprNode.Children.Delete(FRootExprNode.Children.IndexOf(NodesToDelete[i]));
        TABSExprNode(NodesToDelete[i]).Free;
       end;
      if (FRootExprNode.Children.Count = 0) then
       begin
         FRootExprNode.Free;
         FRootExprNode := nil;
       end;
     finally
      NodesToDelete.Free;
     end;
   end
 else
 // IF '='
 if ((FRootExprNode is TABSExprNodeComparison) and
    (FRootExprNode.Operator = doEQ)) then
  begin
    if (FRootExprNode.IsJoinCondition(AO1,AO2)) then
     begin
      // store extracted filter
      TABSExprNodeField(FRootExprNode.Children[0]).FillItem(Item1);
      TABSExprNodeField(FRootExprNode.Children[1]).FillItem(Item2);
      FieldList1.Append(Item1);
      FieldList2.Append(Item2);
      Inc(Result);
      // will delete this node
      FRootExprNode.Free;
      FRootExprNode := nil;
     end;
  end;
end; // ExtractJoinConditions


//------------------------------------------------------------------------------
// create conditions for index scan
//------------------------------------------------------------------------------
procedure TABSExpression.TryCreateIndexScanConditionsFromNode(
                         Node:           TABSExprNode;
                         IndexDefs:      TABSIndexDefs;
                         ScanConditions: TABSScanSearchConditionArray;
                         SessionID: TABSSessionID);
var
  i:  Integer;
begin
  for i := 0 to IndexDefs.Count-1 do
    if (Node.CanUseIndex(0, scNone, IndexDefs[i], SessionID)) then
      Node.AddAsIndexScanCondition(ScanConditions, IndexDefs[i], Self);
end;// TryCreateIndexScanConditionsFromNode


//------------------------------------------------------------------------------
// remove extracted nodes (they included in list, but list contains more)
//------------------------------------------------------------------------------
procedure TABSExpression.RemoveExtractedNodes(ScanConditions: TABSScanSearchConditionArray;
                                  ScanConditionNo, ScanEndConditionNo: Integer);
var
  Node: TABSExprNode;
  i,j,k: Integer;
begin
  for i := 0 to ScanConditions.Count-1 do
    if (i in [ScanConditionNo, ScanEndConditionNo]) then
      for j := 0 to ScanConditions.Items[i].ParentExpressions.Count-1 do
        if (ScanConditions.Items[i].ParentExpressions[j] = Self) then
          begin
            Node := TABSExprNode(ScanConditions.Items[i].ParentExpressionNodes[j]);
            if (Node <> FRootExprNode) then
             begin
               k := FRootExprNode.Children.IndexOf(Node);
// bug fix attempt: added in 4.84             
{               if (k < 0) then
                 raise EABSException.Create(20059, ErrorAInvalidIndexOfNo)
               else}
               if (k >= 0) then
                FRootExprNode.Children.Delete(k);
             end
            else
             FRootExprNode := nil;
          end;
end;// RemoveExtractedNodes


//------------------------------------------------------------------------------
// create conditions for index scan
//------------------------------------------------------------------------------
procedure TABSExpression.CreateIndexScanConditions(
                         IndexDefs:               TABSIndexDefs;
                         ScanConditions:          TABSScanSearchConditionArray;
                         SessionID:               TABSSessionID);

procedure TryNode(NodeToTry: TABSExprNode);
var
  i: Integer;
begin
  if (NodeToTry is TABSExprNodeComparison) then
    TryCreateIndexScanConditionsFromNode(NodeToTry,IndexDefs,
                              ScanConditions,SessionID)
  else
    if (NodeToTry is TABSExprNodeBoolean) then
      if (NodeToTry.Operator in [doISNULL, doISNOTNULL]) then
        TryCreateIndexScanConditionsFromNode(NodeToTry,IndexDefs,
                              ScanConditions,SessionID)
      else
        if (NodeToTry.Operator = doAND) then
          for i := 0 to NodeToTry.Children.Count-1 do
            TryNode(NodeToTry.Children[i]);
end;

begin
  TryNode(FRootExprNode);
end;// CreateIndexScanConditions


//------------------------------------------------------------------------------
// try to add conditions on another index fields
//------------------------------------------------------------------------------
function TABSExpression.ExtendMultiFieldIndexConditionFromNode(Node: TABSExprNode;
   ScanCondition: TABSScanSearchCondition; IndexDef: TABSIndexDef): Boolean;
begin
  if (ScanCondition.KeyFieldCount < IndexDef.ColumnCount) then
    begin
      Result := Node.CanUseIndex(ScanCondition.KeyFieldCount, ScanCondition.Condition, IndexDef);
      if (Result) then
        Node.ExtendIndexScanCondition(ScanCondition, IndexDef, Self);
    end
  else
    Result := False;
end;// ExtendMultiFieldIndexConditionFromNode


//------------------------------------------------------------------------------
// try to add conditions on another index fields
//------------------------------------------------------------------------------
function TABSExpression.ExtendMultiFieldIndexCondition(
    ScanCondition: TABSScanSearchCondition; IndexDef: TABSIndexDef): Boolean;

function TryNode(NodeToTry: TABSExprNode): Boolean;
var
  i: Integer;
begin
  Result := False;
  if (NodeToTry is TABSExprNodeComparison) then
    Result := ExtendMultiFieldIndexConditionFromNode(NodeToTry, ScanCondition,IndexDef)
  else
    if (NodeToTry is TABSExprNodeBoolean) then
      if (NodeToTry.Operator in [doISNULL, doISNOTNULL]) then
        Result := ExtendMultiFieldIndexConditionFromNode(NodeToTry, ScanCondition,IndexDef)
      else
        if (NodeToTry.Operator = doAND) then
          for i := 0 to NodeToTry.Children.Count-1 do
            Result := Result or TryNode(NodeToTry.Children[i]);
end;

begin
  Result := TryNode(FRootExprNode);
end;// ExtendMultiFieldIndexCondition


//------------------------------------------------------------------------------
// add extracted node
//------------------------------------------------------------------------------
procedure TABSExpression.AddNode(Node: TABSExprNode);
begin
   if (FRootExprNode <> nil) then
     FRootExprNode.Children.Add(Node)
   else
     FRootExprNode := Node;
end;// AddNode


//------------------------------------------------------------------------------
// add and move nodes from another expression: self => (self AND Expr2)
//------------------------------------------------------------------------------
procedure TABSExpression.AddAndMoveNodesFromExpression(Expr: TABSExpression);
var
  Node: TABSExprNode;
begin
   if (FRootExprNode = nil) then
     FRootExprNode := Expr.FRootExprNode
   else
     begin
      // If Root Expr is not 'AND' => create it
      if not ((FRootExprNode is TABSExprNodeBoolean) and (FRootExprNode.Operator = doAND)) then
        begin
          Node := TABSExprNodeBoolean.Create(doAND);
          Node.Children.Add(FRootExprNode);
          FRootExprNode := Node;
        end;
      // add nodes from another expression
      FRootExprNode.Children.Add(Expr.FRootExprNode);
      // move AND nodes to the root node children
      MoveAndNodesToRoot;
     end;
   Expr.FRootExprNode := nil;
end;// AddAndMoveNodesFromExpression


//------------------------------------------------------------------------------
// check if agregate expr, const or only grouped fields are used
//------------------------------------------------------------------------------
function TABSExpression.CanBeUsedInGroupBy(GroupByFields: string): Boolean;
begin
   if (FRootExprNode <> nil) then
     Result := FRootExprNode.CanBeUsedInGroupBy(GroupByFields)
   else
     Result := True;
end;// CanBeUsedInGroupBy


//------------------------------------------------------------------------------
// gets current token
//------------------------------------------------------------------------------
function TABSExpression.GetCurrentToken: Boolean;
begin
 Result := LLex.GetCurrentToken(Token);
end;// GetCurrentToken


//------------------------------------------------------------------------------
// gets next token
//------------------------------------------------------------------------------
function TABSExpression.GetNextToken: Boolean;
begin
 Result := LLex.GetNextToken(Token);
end;// GetNextToken


//------------------------------------------------------------------------------
// gets token and looks at next token with check for token type restrictions
//------------------------------------------------------------------------------
function TABSExpression.GetNextToken(PermittedTypes: TTokenTypes;
                                      RaiseExceptions: Boolean = False): Boolean;
begin
  // get next token
  Result := LLex.GetNextToken(Token);
  if (not Result) then
   if (RaiseExceptions) then
      raise EABSException.Create(30063, ErrorGUnexpectedEndOfCommand,
                                 [Token.LineNum, Token.ColumnNum]);
  // check token type
  if (Result) then
   if ((PermittedTypes <> [])) then
    if not (Token.TokenType in PermittedTypes) then
      if (RaiseExceptions) then
        raise EABSException.Create(30064, ErrorGUnexpectedToken,
                                   [Token.Text, Token.LineNum, Token.ColumnNum])
      else
         Result := False;
end;// GetNextToken


//------------------------------------------------------------------------------
// saves internal state (to restore in case of not successful forward parsing)
//------------------------------------------------------------------------------
procedure TABSExpression.SaveState(var SavedTokenNo: integer);
//  var SavedRootNode: TABSExprNode);
begin
 // save token No
 SavedTokenNo := LLex.GetCurrentTokenNo;
 // save root node
 //SavedRootNode := FRootNode;
end;// SaveState


//------------------------------------------------------------------------------
// restores internal state (in case of not successful forward parsing)
//------------------------------------------------------------------------------
procedure TABSExpression.RestoreState(SavedTokenNo: integer);
//  SavedRootNode: TABSExprNode);
begin
 // restore token No
 LLex.SetCurrentTokenNo(SavedTokenNo, Token);
 // restore root node
 //FRootNode.DeleteTo(SavedRootNode);
 //FRootNode := SavedRootNode;
end;// RestoreState


//------------------------------------------------------------------------------
// parses <,>,=,<>,>=,<=
//------------------------------------------------------------------------------
function TABSExpression.ParseCompOp: TABSDataOperator;
begin
  if (Token.Text = '<') then
   Result := doLT
  else
  if (Token.Text = '>') then
   Result := doGT
  else
  if (Token.Text = '=') then
   Result := doEQ
  else
  if (Token.Text = '<>') then
   Result := doNE
  else
  if (Token.Text = '<=') then
   Result := doLE
  else
  if (Token.Text = '>=') then
   Result := doGE
  else
   Result := doUNDEFINED;

  if (Result <> doUNDEFINED) then
   GetNextToken;
end;// ParseCompOp


//------------------------------------------------------------------------------
// parses ALL, ANY, SOME
//------------------------------------------------------------------------------
function TABSExpression.ParseQuantifier: TABSQuantifier;
begin
  if (IsReservedWord(Token, rwALL)) then
   Result := qALL
  else
  if (IsReservedWord(Token, rwANY)) then
   Result := qANY
  else
  if (IsReservedWord(Token, rwSOME)) then
   Result := qANY
  else
   Result := qUNDEFINED;

  if (Result <> qUNDEFINED) then
   GetNextToken;
end;// ParseQuantifier


//------------------------------------------------------------------------------
// parses <row value constructor element>
//------------------------------------------------------------------------------
function TABSExpression.ParseRowValueConstructorElement: TABSExprNode;
begin
  Result := ParseNullConst;
  if (Result = nil) then
    Result := ParseValueExpression;
end;// ParseRowValueConstructorElement


//------------------------------------------------------------------------------
// parses <row subquery>
//------------------------------------------------------------------------------
function TABSExpression.ParseRowSubquery(MustHaveOneRow: Boolean; MustHaveOneField: Boolean=True): TABSExprNode;
var
  SavedTokenNo: integer;        // saved/restored information
  i, BracketsToSkip: Integer;
begin
  // save current state
  SaveState(SavedTokenNo);
  // try to parse select
  Result := nil;
  // skip '('
  BracketsToSkip := 0;
  while (Token.TokenType = tktLeftParenthesis) do
    begin
      GetNextToken;
      Inc(BracketsToSkip);
    end;
  if (IsReservedWord(Token, rwSELECT)) then
    begin
      Result := TABSExprNodeSubquery.Create(MustHaveOneField, MustHaveOneRow, LDataset, LLex);
      GetCurrentToken;
    end;
  // skip ')'
  for i:=0 to BracketsToSkip-1 do
    GetNextToken;
  // restore original state?
  if (Result = nil) then
    RestoreState(SavedTokenNo);
end;// ParseRowSubquery


//------------------------------------------------------------------------------
// parses <row value constructor element> | <row subquery>
//------------------------------------------------------------------------------
function TABSExpression.ParseRowValueConstructor: TABSExprNode;
begin
  Result := ParseRowSubquery(True);
  if (Result = nil) then
    Result := ParseRowValueConstructorElement;
end;// ParseRowValueConstructor


//------------------------------------------------------------------------------
// parses <row value constructor> <comp op> <quantifier> <row value constructor>
//------------------------------------------------------------------------------
function TABSExpression.ParseQuantifiedComparisonPredicate: TABSExprNode;
var
  LeftNode, RightNode: TABSExprNode;
  CompOp: TABSDataOperator;
  Quantifier: TABSQuantifier;
  bOK: Boolean;
begin
 // parse <row value constructor>
 RightNode := nil;
 LeftNode := ParseRowValueConstructor;
 bOK := False;
 if (LeftNode <> nil) then
  begin
   // <,>,=,<>,>=,<=
   CompOp := ParseCompOp;
   // ALL, ANY, SOME
   Quantifier := ParseQuantifier;
   // parse 2nd <row value constructor>
   if ((CompOp <> doUndefined) and (Quantifier <> qUndefined)) then
    begin
     RightNode := ParseRowSubquery(False);
     if (RightNode <> nil) then
      begin
       // create <CompOp> node
       LeftNode := TABSExprNodeComparison.Create(CompOp, Quantifier, LeftNode, RightNode,
                                             F3ValueLogic, FCaseInsensitive, FPartialKey);
       bOK := True;
      end
    end
  end;
 if (not bOK) then
   begin
     if (LeftNode <> nil) then
       LeftNode.Free;
     LeftNode := nil;
     if (RightNode <> nil) then
       RightNode.Free;
   end;
 Result := LeftNode;
end;// ParseQuantifiedComparisonPredicate


//------------------------------------------------------------------------------
// parses <row value constructor> <comp op> <row value constructor>
//------------------------------------------------------------------------------
function TABSExpression.ParseComparisonPredicate: TABSExprNode;
var
  LeftNode, RightNode: TABSExprNode;
  CompOp: TABSDataOperator;
  pk: Boolean;
  F3ValLogic: Boolean;

 function IsPartialKey(Node: TABSExprNode): integer;
 var i: Integer;
 begin
  Result := 0;
  if Node.ClassType = TABSExprNodeConst then
   begin
    i := Node.Value.StrLen;
    if (i <> -1) then
     if Node.Value.AsAnsiString[i] = '*' then
      begin
       if Node.Value.IsWideStringDataType then
         Node.Value.AsWideString := Copy(Node.Value.AsWideString,1,i-1)
       else
         Node.Value.AsAnsiString := Copy(Node.Value.AsAnsiString,1,i-1);
       Result := 1;
      end;
   end;
 end;

begin
 // parse <row value constructor>
 LeftNode := ParseRowValueConstructor;
 if (LeftNode <> nil) then
  begin
   // <,>,=,<>,>=,<=
   CompOp := ParseCompOp;
   // parse 2nd <row value constructor>
   if (CompOp <> doUndefined) then
    begin
     RightNode := ParseRowValueConstructor;
     if (RightNode <> nil) then
      begin
        pk := False;
       // Detect Partial Key
       if FPartialKey then
        pk := (IsPartialKey(LeftNode) + IsPartialKey(RightNode) > 0);

       // replace StrField = '' => StrField IS NULL, StrField <> '' => StrField IS NOT NULL
       if (LeftNode is TABSExprNodeField) and
          (RightNode is TABSExprNodeConst) and (RightNode.GetDataValue.IsNull) and
          (RightNode.Value.IsStringDataType or RightNode.Value.IsWideStringDataType or RightNode.Value.IsDateTimeDataType) and
          (CompOp in [doEQ,doNE]) then
         begin
           RightNode.Free;
           if (CompOp = doEQ) then
             LeftNode := TABSExprNodeBoolean.Create(doISNULL, LeftNode)
           else
             LeftNode := TABSExprNodeBoolean.Create(doISNOTNULL, LeftNode);
         end
       else
       if (RightNode is TABSExprNodeField) and
          (LeftNode is TABSExprNodeConst) and (LeftNode.GetDataValue.IsNull) and
          (LeftNode.Value.IsStringDataType or LeftNode.Value.IsWideStringDataType or LeftNode.Value.IsDateTimeDataType) and
          (CompOp in [doEQ,doNE]) then
         begin
           LeftNode.Free;
           if (CompOp = doEQ) then
             LeftNode := TABSExprNodeBoolean.Create(doISNULL, RightNode)
           else
             LeftNode := TABSExprNodeBoolean.Create(doISNOTNULL, RightNode);
         end
       else
         begin
           // StrField > '' => apply 2-value logic
           // bug-fix 4.87
           // StrField compare_op StrConst => apply 2-value logic
           F3ValLogic := not ((LeftNode is TABSExprNodeField) and (RightNode is TABSExprNodeConst) and (RightNode.Value.IsStringDataType or RightNode.Value.IsWideStringDataType or RightNode.Value.IsDateTimeDataType or RightNode.Value.IsNull)) and
                         not ((RightNode is TABSExprNodeField) and (LeftNode is TABSExprNodeConst) and (LeftNode.Value.IsStringDataType or LeftNode.Value.IsWideStringDataType or LeftNode.Value.IsDateTimeDataType or LeftNode.Value.IsNull));
           // string compared with null => apply 2-value logic
           if (RightNode.Value.IsStringDataType or RightNode.Value.IsWideStringDataType) and (LeftNode.Value.IsNull) then F3ValLogic := False;
           if (LeftNode.Value.IsStringDataType or LeftNode.Value.IsWideStringDataType) and (RightNode.Value.IsNull) then F3ValLogic := False; 
           // create <CompOp> node
           LeftNode := TABSExprNodeComparison.Create(CompOp, LeftNode, RightNode,
                                               F3ValLogic, FCaseInsensitive, pk);
         end;
      end
     else
      begin
       LeftNode.Free;
       LeftNode := nil;
      end;
    end
   else
    begin
     LeftNode.Free;
     LeftNode := nil;
    end;
  end;
 Result := LeftNode;
end;// ParseComparisonPredicate


//------------------------------------------------------------------------------
// parses <match value> [ NOT ] LIKE <pattern> [ ESCAPE <escape character> ]
//------------------------------------------------------------------------------
function TABSExpression.ParseLikePredicate: TABSExprNode;
var
  Node, LeftNode, RightNode: TABSExprNode;
  bNot: Boolean;
  EscapeStr: string;
  CompOp: TABSDataOperator;
begin
 // parse <character value expression>
 LeftNode := ParseRowValueConstructor;
 if (LeftNode <> nil) then
  begin
   // [NOT]
   if (IsReservedWord(Token, rwNOT)) then
     begin
      bNot := True;
      GetNextToken;
     end
   else
     bNot := False;
   // LIKE
   if (IsReservedWord(Token, rwLIKE)) then
    begin
     // get <pattern>
     GetNextToken;
     RightNode := ParseRowValueConstructor;

     // ESCAPE
     if (IsReservedWord(Token, rwESCAPE)) then
      begin
       // get <escape character>
       GetNextToken;
       if (Token.TokenType <> tktParameter) then
         EscapeStr := Token.Text
       else
         EscapeStr := Token.ParamValue.AsWideString;
       if (Length(EscapeStr) <> 1) then
         begin
           LeftNode.Free;
           RightNode.Free;
           raise EABSException.Create(20294,ErrorAInvalidEscapeCharacter,[Token.LineNum, Token.ColumnNum]);
         end;
       GetNextToken;
      end
     else
       EscapeStr := '';

     // (F LIKE 'str%') => (F = 'str') with partial compare
     if (EscapeStr = '') and
        (RightNode is TABSExprNodeConst) and
        (TABSExprNodeConst(RightNode).IsLikePatternEqualToPartialComparePattern) then
       begin
           if (not bNot) then
             CompOp := doEQ
           else
             CompOp := doNE;
           TABSExprNodeConst(RightNode).TruncateForPartialCompare;
           LeftNode := TABSExprNodeComparison.Create(CompOp, LeftNode, RightNode,
                                               False, FCaseInsensitive, True);
       end
     else
       begin
         if (not bNot) then
           Node := TABSExprNodeBoolean.Create(doLIKE, FCaseInsensitive, false, EscapeStr)
         else
           Node := TABSExprNodeBoolean.Create(doNOTLIKE, FCaseInsensitive, false, EscapeStr);
         Node.Children.Add(LeftNode);
         Node.Children.Add(RightNode);
         LeftNode := Node;
       end;
    end
   else
    begin
      LeftNode.Free;
      LeftNode := nil;
    end;
  end;
 Result := LeftNode;
end;// ParseLikePredicate


//------------------------------------------------------------------------------
// parses <row value constructor> IS [ NOT ] NULL
//------------------------------------------------------------------------------
function TABSExpression.ParseNullPredicate: TABSExprNode;
var
  LeftNode: TABSExprNode;
  bNot: Boolean;
  bOK: Boolean;
begin
 // parse <row value constructor>
 LeftNode := ParseRowValueConstructor;
 bOK := False;
 if (LeftNode <> nil) then
  begin
   // IS?
   if (IsReservedWord(Token, rwIS)) then
    begin
     // get next token
     GetNextToken;
     // [NOT]
     if (IsReservedWord(Token, rwNOT)) then
      begin
       bNot := True;
       GetNextToken;
      end
     else
      bNot := False;
     // NULL
     if (IsReservedWord(Token, rwNULL)) then
      begin
       // skip token NULL
       GetNextToken;
       if (not bNot) then
        LeftNode := TABSExprNodeBoolean.Create(doISNULL, LeftNode)
       else
        LeftNode := TABSExprNodeBoolean.Create(doISNOTNULL, LeftNode);
       bOk := True;
      end;
    end;
  end;
 if (not bOK) then
    begin
      if (LeftNode <> nil) then
       LeftNode.Free;
      LeftNode := nil;
    end;
 Result := LeftNode;
end;// ParseNullPredicate


//------------------------------------------------------------------------------
// parses <between predicate>
//------------------------------------------------------------------------------
function TABSExpression.ParseBetweenPredicate: TABSExprNode;
var
  arg1, arg2, arg3: TABSExprNode;
  bNot: boolean;
begin
{
<between predicate> ::=
    <row value constructor> [ NOT ] BETWEEN
      <row value constructor> AND <row value constructor>
}
 Result := nil;
 arg2:=nil; arg3:=nil;
 arg1 := ParseRowValueConstructor;
 if arg1 <> nil then
  begin
   // [NOT]
   if (IsReservedWord(Token, rwNOT)) then
    begin
     bNot := True;
     // and Get next token
     GetNextToken;
    end
   else
    bNot := False;
   // BETWEEN
   if (IsReservedWord(Token, rwBETWEEN)) then
    begin
     // get next token
     GetNextToken;
     arg2 := ParseRowValueConstructor;
     if arg2 <> nil then
      begin
       // AND
       if (IsReservedWord(Token, rwAND)) then
        begin
         // get next token
         GetNextToken;
         arg3 := ParseRowValueConstructor;
         if arg3 <> nil then
          if bNot then
           Result := TABSExprNodeBoolean.Create(doNOTBETWEEN, arg1, arg2, arg3)
          else
           Result := TABSExprNodeBoolean.Create(doBETWEEN, arg1, arg2, arg3);
        end;
      end
    end;
  end;
 if Result = nil then
  begin
   if arg1 <> nil then arg1.Free;
   if arg2 <> nil then arg2.Free;
   if arg3 <> nil then arg3.Free;
  end;
end;//ParseBetWeenPredicate


//------------------------------------------------------------------------------
// parses <in predicate>
//------------------------------------------------------------------------------
function TABSExpression.ParseInPredicate: TABSExprNode;
var
  bNot: boolean;
  arg1: TABSExprNode;
  arg2: TABSExprNode;
begin
{
<in predicate> ::=
    <row value constructor>
      [ NOT ] IN <in predicate value>
}
  Result := nil;
  arg1 := ParseRowValueConstructor;
  if arg1 <> nil then
   begin
    // [NOT]
    if (IsReservedWord(Token, rwNOT)) then
     begin
      bNot := True;
      // and Get next token
      GetNextToken;
     end
    else
     bNot := False;

    // IN
    if (IsReservedWord(Token, rwIN)) then
     begin
      // get next token
      GetNextToken;
      // '(' ?
      if (Token.TokenType = tktLeftParenthesis) then
       begin
        // get next token
        GetNextToken;
        if (IsReservedWord(Token, rwSELECT)) then
          begin
            arg2 := TABSExprNodeSubquery.Create(True, False, LDataset, LLex);
            if bNot then
              Result := TABSExprNodeBoolean.Create(doNOTIN, arg1, arg2)
            else
              Result := TABSExprNodeBoolean.Create(doIN, arg1, arg2);
            GetCurrentToken;
            // ')'
            if Token.TokenType = tktRightParenthesis then
             GetNextToken;
          end
        else
          begin
            if bNot then
              Result := TABSExprNodeBoolean.Create(doNOTIN, arg1)
            else
              Result := TABSExprNodeBoolean.Create(doIN, arg1);
            repeat
             arg1 := ParseValueExpression;
             // if error
             if arg1 = nil then
              begin
               Result.Free;
               Result := nil;
              end;
             // add argument to IN node
             Result.Children.add(arg1);
             arg1 := nil;
             // ',' ?
             if Token.TokenType = tktComma then
              // get next token
              GetNextToken
             else
             // ')'
             if Token.TokenType = tktRightParenthesis then
              break;
            until false;
            // get next token
            GetNextToken
           end;
          end;
     end;

    if (Result = nil) and (arg1 <> nil) then
     begin
      arg1.Free;
     end;
   end;
end;//ParseInPredicate


//------------------------------------------------------------------------------
// parses <exists predicate>
//------------------------------------------------------------------------------
function TABSExpression.ParseExistsPredicate: TABSExprNode;
var
  arg1: TABSExprNode;
  i, BracketCount: Integer;
begin
{
<exists predicate> ::= EXISTS <table subquery>
}
  Result := nil;
  if (IsReservedWord(Token, rwEXISTS)) then
    begin
      // and Get next token
      GetNextToken;
      BracketCount := 0;
      // skip '('
      while (Token.TokenType = tktLeftParenthesis) do
        begin
          Inc(BracketCount);
          GetNextToken;
        end;
      arg1 := TABSExprNodeSubquery.Create(False, False, LDataset, LLex);
      GetCurrentToken;
      // skip ')'
      for i:=0 to BracketCount-1 do
        if (Token.TokenType = tktRightParenthesis) then
          GetNextToken
        else
          raise EABSException.Create(30492, ErrorGMissingRightParenthesis,
                                   [Token.LineNum, Token.ColumnNum]);
      Result := TABSExprNodeBoolean.Create(doEXISTS, arg1);
    end;
end;// ParseExistsPredicate


//------------------------------------------------------------------------------
// parses <true/false>
//------------------------------------------------------------------------------
function TABSExpression.ParseTrueFalseConst: TABSExprNode;
begin
 Result := nil;
 if Token.ReservedWord in [rwTRUE, rwFALSE] then
   begin
     Result := TABSExprNodeConst.Create;
     Result.Value.AsBoolean := (Token.ReservedWord = rwTRUE);
     GetNextToken;
   end
 else if (Token.TokenType = tktParameter) then
   begin
     if (Token.ParamValue.DataType = bftLogical) then
       begin
         Result := TABSExprNodeConst.Create;
         Result.Value.Assign(Token.ParamValue);
         GetNextToken;
       end;
   end
end;//ParseTrueFalseConst


//------------------------------------------------------------------------------
// parses NULL const
//------------------------------------------------------------------------------
function TABSExpression.ParseNullConst: TABSExprNode;
begin
 if (Token.ReservedWord = rwNULL) or
    ((Token.TokenType = tktParameter) and (Token.ParamValue.IsNull)) then
   begin
     Result := TABSExprNodeConst.Create;
     if (Token.TokenType = tktParameter) and (Token.ParamValue.DataType <> bftUnknown) then
       Result.Value.DataType := Token.ParamValue.DataType
     else
       Result.Value.DataType := bftSignedInt32;
     GetNextToken;
   end
 else
   Result := nil;
end;//ParseNullConst


//------------------------------------------------------------------------------
// parses <comparison predicate> | <between predicate> | <in predicate>  |
// <like predicate>  | <null predicate> | <quantified comparison predicate> |
// <exists predicate> | <unique predicate> | <match predicate> | <overlaps predicate>
//------------------------------------------------------------------------------
function TABSExpression.ParsePredicate: TABSExprNode;
var
  SavedTokenNo: integer;        // saved/restored information
  //SavedRootNode: TABSExprNode; // saved/restored information
begin
 // save current state
 SaveState(SavedTokenNo);
 Result := ParseTrueFalseConst;
 if (Result = nil) then
  begin
    // restore original state
    RestoreState(SavedTokenNo);
    // try to parse next tokens as a quantified comparison predicate
    Result := ParseQuantifiedComparisonPredicate;
  end;
 if (Result = nil) then
  begin
    // restore original state
    RestoreState(SavedTokenNo);
    // try to parse next tokens as a comparison predicate
    Result := ParseComparisonPredicate;
  end;
 // if failed comparison - try like
 if (Result = nil) then
  begin
    // restore original state
    RestoreState(SavedTokenNo);
    // try to parse next tokens as a like predicate
    Result := ParseLikePredicate;
  end;
 // if failed like - try null
 if (Result = nil) then
  begin
    // restore original state
    RestoreState(SavedTokenNo);
    // try to parse next tokens as a null predicate
    Result := ParseNullPredicate;
  end;
 // if failed null - try between
 if (Result = nil) then
  begin
    // restore original state
    RestoreState(SavedTokenNo);
    // try to parse next tokens as a between predicate
    Result := ParseBetweenPredicate;
  end;
 // if failed between - try in
 if (Result = nil) then
  begin
    // restore original state
    RestoreState(SavedTokenNo);
    // try to parse next tokens as a IN predicate
    Result := ParseInPredicate;
  end;
 // if failed in - try exists
 if (Result = nil) then
  begin
    // restore original state
    RestoreState(SavedTokenNo);
    // try to parse next tokens as a EXISTS predicate
    Result := ParseExistsPredicate;
  end;
end;// ParsePredicate


//------------------------------------------------------------------------------
// parses <predicate>  | <left paren> <search condition> <right paren> | <row value constructor>
//------------------------------------------------------------------------------
function TABSExpression.ParseBooleanPrimary: TABSExprNode;
var
  SavedTokenNo: integer;        // saved/restored information
begin
 // save current state
 SaveState(SavedTokenNo);
 // <predicate>
 Result := ParsePredicate;
 // <left paren> <search condition> <right paren>
 if (Result = nil) then
   begin
     // restore original state
     RestoreState(SavedTokenNo);
     // '(' ?
     if (Token.TokenType = tktLeftParenthesis) then
      begin
       // skip '('
       GetNextToken;
       // parse <search condition>
       Result := ParseSearchCondition;
       if (Result = nil) then
        begin
          // restore original state
          RestoreState(SavedTokenNo);
          // try to parse next tokens as a predicate
          Result := ParsePredicate;
        end
       else
         // skip ')'
         GetNextToken;
      end
   end;
 // <row value constructor> - (i.e. <boolean field>)
 if (Result = nil) then
   begin
     // restore original state
     RestoreState(SavedTokenNo);
     Result := ParseRowValueConstructor;
   end;
end;// ParseBooleanPrimary


//------------------------------------------------------------------------------
// parses <boolean primary> [ IS [ NOT ] <truth value> ]
//------------------------------------------------------------------------------
function TABSExpression.ParseBooleanTest: TABSExprNode;
var
  bNot: Boolean;
  bIsNull: Boolean;
  bIsNotNull: Boolean;
  LeftNode: TABSExprNode;
begin
  // parse <boolean primary>
  LeftNode := ParseBooleanPrimary;
  if (LeftNode <> nil) then
    begin
      bNot := False;
      bIsNull := False;
      bIsNotNull := False;
      // [ IS [ NOT ] <truth value> ] ?
      if (IsReservedWord(Token, rwIS)) then
        begin
          // skip 'IS' token
          GetNextToken([tktReservedWord]);
          if (IsReservedWord(Token, rwNOT)) then
           begin
            bNot := True;
            // skip 'NOT' token
            GetNextToken([tktReservedWord]);
           end;
          // TRUE | FALSE | UNKNOWN ?
          if (IsReservedWord(Token, rwTRUE)) then
            // skip 'TRUE' token
            GetNextToken
          else
          if (IsReservedWord(Token, rwFALSE)) then
           begin
            bNot := not bNot;
            // skip 'FALSE' token
            GetNextToken
           end
          else
          if (IsReservedWord(Token, rwUNKNOWN)) then
           begin
            if (bNot) then
              bIsNotNull := True
            else
              bIsNull := True;
            bNot := False;
            // skip 'UNKNOWN' token
            GetNextToken
           end
          else
           raise EABSException.Create(30067, ErrorGOtherTokenExpected,
             ['TRUE, FALSE or UNKNOWN', Token.Text, Token.LineNum, Token.ColumnNum]);
        end;

      // create node?
      if (bNot) then
       Result := TABSExprNodeBoolean.Create(doNOT, LeftNode)
      else
      if (bIsNull) then
       Result := TABSExprNodeBoolean.Create(doISNULL, LeftNode)
      else
      if (bIsNotNull) then
       Result := TABSExprNodeBoolean.Create(doISNOTNULL, LeftNode)
      else
       Result := LeftNode;
    end
  else
    Result := nil;
end;// ParseBooleanTest


//------------------------------------------------------------------------------
// parses [ NOT ] <boolean test>
//------------------------------------------------------------------------------
function TABSExpression.ParseBooleanFactor: TABSExprNode;
var
  bNot: Boolean;
  LeftNode: TABSExprNode;
begin
  // [NOT]?
  if (IsReservedWord(Token, rwNOT)) then
    begin
      // skip 'NOT' token
      GetNextToken;
      bNot := True;
    end
  else
    bNot := False;
  // parse <boolean test>
  LeftNode := ParseBooleanTest;
  // create <NOT> node?
  if (bNot and (LeftNode <> nil)) then
   Result := TABSExprNodeBoolean.Create(doNOT, LeftNode)
  else
   Result := LeftNode;
end;// ParseBooleanFactor


//------------------------------------------------------------------------------
// parses <boolean factor> | <boolean term> AND <boolean factor>
//------------------------------------------------------------------------------
function TABSExpression.ParseBooleanTerm: TABSExprNode;
var
  Node: TABSExprNode;
  AndOperatorNode: TABSExprNode;
begin
 AndOperatorNode := nil;
 // parse <boolean factor>
 Node := ParseBooleanFactor;
 if (Node <> nil) then
  begin
   // { AND <boolean term> }
   if IsReservedWord(Token, rwAND) then
    begin
     AndOperatorNode := TABSExprNodeBoolean.Create(doAND, Node);
     while (IsReservedWord(Token, rwAND)) do
      begin
        // skip 'AND' token
        GetNextToken;
        // parse <boolean factor>
        Node := ParseBooleanFactor;
        if (Node = nil) then
         raise EABSException.Create(30069, ErrorGBooleanExpressionExpected,
                                  [Token.Text, Token.LineNum, Token.ColumnNum]);
        // add into <AND> node
        AndOperatorNode.Children.Add(Node);
      end;
    end
   else
    AndOperatorNode := Node;
  end;
 Result := AndOperatorNode;
end;// ParseBooleanTerm


//------------------------------------------------------------------------------
// parses <boolean term> | <search condition> OR <boolean term>
//------------------------------------------------------------------------------
function TABSExpression.ParseSearchCondition: TABSExprNode;
var
  LeftNode, RightNode: TABSExprNode;
begin
 // <boolean term>
 LeftNode := ParseBooleanTerm;
 if (LeftNode <> nil) then
  begin
   // { OR <boolean term> }
   while (IsReservedWord(Token, rwOR)) do
    begin
      // skip 'OR' token
      GetNextToken;
      // parse <boolean term>
      RightNode := ParseBooleanTerm;
      if (RightNode = nil) then
       raise EABSException.Create(30070, ErrorGBooleanExpressionExpected,
                                  [Token.Text, Token.LineNum, Token.ColumnNum]);
      // create <OR> node
      LeftNode := TABSExprNodeBoolean.Create(doOR, LeftNode, RightNode);
    end;
  end;
 Result := LeftNode;
end;// ParseSearchCondition


//------------------------------------------------------------------------------
// parses <value expression>
//------------------------------------------------------------------------------
function TABSExpression.ParseValueExpression: TABSExprNode;
var
 SavedTokenNo: integer;        // saved/restored information
begin
{
<value expression> ::=
      <numeric value expression>
    | <string value expression>
    | <datetime value expression>
    | <interval value expression>
}
 SaveState(SavedTokenNo);
 Result := ParseNumericValueExpression;
 {if (Result = nil) then
  begin
    // restore original state
    RestoreState(SavedTokenNo);
    Result := ParseSearchCondition;
  end;}
 if (Result = nil) then
  begin
    // restore original state
    RestoreState(SavedTokenNo);
    Result := ParseIntervalValueExpression;
  end;
 if (Result = nil) then
  begin
    // restore original state
    RestoreState(SavedTokenNo);
    Result := ParseDatetimeValueExpression;
  end;
 if (Result = nil) then
  begin
    // restore original state
    RestoreState(SavedTokenNo);
    Result := ParseStringValueExpression;
  end;
 if (Result = nil) then
  begin
    // restore original state
    RestoreState(SavedTokenNo);
    Result := ParseTrueFalseConst;
  end;
 if (Result = nil) then
  begin
    // restore original state
    RestoreState(SavedTokenNo);
    Result := ParseNullConst;
  end;
 if (Result = nil) then
  begin
    RestoreState(SavedTokenNo);
    Result := ParseBlobExpression;
  end;
end;// ParseValueExpression

//------------------------------------------------------------------------------
// parses <numeric value expression>
//------------------------------------------------------------------------------
function TABSExpression.ParseNumericValueExpression: TABSExprNode;
var
  Term: TABSExprNode;
begin
{
<numeric value expression> ::=
      <term>
    | <numeric value expression> <plus sign> <term>
    | <numeric value expression> <minus sign> <term>
}
  Result:=nil;
  Term := ParseTerm;
  if Term <> nil then
   begin
    if (Token.Text='+') or (Token.Text='-') then
     begin
      Result := TABSExprNodeArithmetic.Create(doADD, Term);
      while (Token.Text='+') or (Token.Text='-') do
       begin
         Term := ParseTerm;
         if Term = nil then
          begin
           Result.Free;
           Result := nil;
           break;
          end;
         Result.Children.Add(Term);
       end;
     end
    else
     Result := Term;
    if Token.Text='||' then
     begin
      Result.Free;
      Result := nil;
     end;
   end
end;//ParseNumericValueExpression

//------------------------------------------------------------------------------
// parses <term>
//------------------------------------------------------------------------------
function TABSExpression.ParseTerm: TABSExprNode;
var
  Factor: TABSExprNode;
  op: TABSDataOperator;
begin
{
<term> ::=
      <factor>
    | <term> <asterisk> <factor>
    | <term> <solidus> <factor>
}
  Result:=ParseFactor;
  if Result <> nil then
    while (Token.Text='*') or (Token.Text='/') do
     begin
      if Token.Text='*' then
        op := doMUL
      else
        op := doDIV;
      // get next token
      GetNextToken;
      Result := TABSExprNodeArithmetic.Create(op, Result);
      Factor := ParseFactor;
//!!      if Factor = nil then ;
      Result.Children.Add(Factor);
     end;
end;//ParseTerm

//------------------------------------------------------------------------------
// parses <factor>
//------------------------------------------------------------------------------
function TABSExpression.ParseFactor: TABSExprNode;
var
  minus: boolean;
begin
{
<factor> ::=
    [ <sign> ] <numeric primary>
}
  minus := false;
  if ParseSign = sgnMinus then minus := true;
  Result:=ParseNumericPrimary;
  if (result <> nil) and minus then
    Result := TABSExprNodeArithmetic.Create(doSUB, Result);

end;//ParseFactor

//------------------------------------------------------------------------------
// parses <sign>
//------------------------------------------------------------------------------
function TABSExpression.ParseSign: TSign;
begin
  if Token.Text = '+' then Result := sgnPlus
  else
  if Token.Text = '-' then Result := sgnMinus
  else Result := sgnUndefined;
  if Result <> sgnUndefined then GetNextToken;
end;//ParseSign

//------------------------------------------------------------------------------
// parses <numeric primary>
//------------------------------------------------------------------------------
function TABSExpression.ParseNumericPrimary: TABSExprNode;
var
  SavedTokenNo: integer;        // saved/restored information
begin
{
<numeric primary> ::=
      <value expression primary>
    | <numeric value function>
}
  Result := ParseValueExpressionPrimary;
  // save current state
  SaveState(SavedTokenNo);
  if Result = nil then
   begin
    RestoreState(SavedTokenNo);
    Result := ParseNumericValueFunction;
   end;
  // Check for numeric content condition
  if Result <> nil then
   begin
    if Result.ClassType = TABSExprNodeConst then
     if (not Result.Value.IsNumericDataType) then
      begin
       RestoreState(SavedTokenNo);
       Result.Free;
       Result := nil;
      end;
   end;

end;//ParseNumericPrimary

//------------------------------------------------------------------------------
// parses <value expression primary>
//------------------------------------------------------------------------------
function TABSExpression.ParseValueExpressionPrimary: TABSExprNode;
var
  SavedTokenNo: integer;        // saved/restored information
begin
{
<value expression primary> ::=
      <unsigned value specification>
    | <column reference>
    | <set function specification>
    | <scalar subquery>
    | <cast specification>
    | <case expression>
    | <left paren> <value expression> <right paren>
}
  SaveState(SavedTokenNo);
  // parses <unsigned value specification>
  Result := ParseUnsignedValueSpecification;
  // parses <column reference>
  if Result=nil then
   begin
    RestoreState(SavedTokenNo);
    Result := ParseColumnReference
   end;
  // parses <set function specification>
  if Result=nil then
   begin
    RestoreState(SavedTokenNo);
    Result := ParseSetFunctionSpecification;
   end;
  // parses <scalar subquery>
  if Result = nil then
   begin
    RestoreState(SavedTokenNo);
    Result := ParseRowSubquery(False);
   end;
  // parses <case expression>
  if Result = nil then
   begin
    RestoreState(SavedTokenNo);
    Result := ParseCaseExpression;
   end;
  // parses ( <value expression> )
  if Result=nil then
   begin
    RestoreState(SavedTokenNo);
    // '('
    if Token.TokenType = tktLeftParenthesis then
     begin
      // get next token
      GetNextToken;
      Result := ParseValueExpression;
      if (Result<>nil) and
         (Token.TokenType <> tktRightParenthesis) then
       begin
        Result.Free;
        Result := nil;
//        raise EABSException.Create(30071, ErrorGOtherTokenExpected,
//                              [')', Token.Text, Token.LineNum,Token.ColumnNum]);
       end
      else
        // get next token
        GetNextToken;
     end;
   end;
  // parses <cast specification>
  if Result = nil then
   begin
    RestoreState(SavedTokenNo);
    Result := ParseCastSpecification;
   end;
end;//ParseValueExpressionPrimary

//------------------------------------------------------------------------------
// parses <unsigned value specification>
//------------------------------------------------------------------------------
function TABSExpression.ParseUnsignedValueSpecification: TABSExprNode;
begin
{
<unsigned value specification> ::=
      <unsigned literal>
    | <general value specification>
}
  Result := ParseUnsignedLiteral;
end;//ParseUnsignedValueSpecification

//------------------------------------------------------------------------------
// parses <unsigned literal>
//------------------------------------------------------------------------------
function TABSExpression.ParseUnsignedLiteral: TABSExprNode;
var
  SavedTokenNo: integer;        // saved/restored information
begin
{
<unsigned literal> ::=
      <unsigned numeric literal>
    | <general literal>
}
  // save current state
  SaveState(SavedTokenNo);
  Result := ParseUnsignedNumericLiteral;
  if Result = nil then
   begin
    RestoreState(SavedTokenNo);
    Result := ParseGeneralLiteral;
   end;
end;//ParseUnsignedLiteral


//------------------------------------------------------------------------------
// parses <unsigned numeric literal>
//------------------------------------------------------------------------------
function TABSExpression.ParseUnsignedNumericLiteral: TABSExprNode;
begin
{
<unsigned numeric literal> ::=
      <exact numeric literal>
    | <approximate numeric literal>
}

 // integer
 if (Token.TokenType = tktInt) then
  begin
   Result := TABSExprNodeConst.Create;
   Result.Value.DataType := bftSignedInt64;
   if Token.Text <> '' then
     Result.Value.AsAnsiString := AnsiString(Token.Text);
   if Abs(Result.Value.AsInt64) < Abs(MaxInt) then
     Result.Value.Cast(bftSignedInt32);

   // get next token
   GetNextToken;
  end
 else
 // float
 if (Token.TokenType = tktFloat) then
  begin
   Result := TABSExprNodeConst.Create;
   Result.Value.DataType := bftDouble;
   Result.Value.AsAnsiString := AnsiString(Token.Text);
   // get next token
   GetNextToken;
  end
 else
 // Parameter
 if ((Token.TokenType = tktParameter) and
     (Token.ParamValue.IsNumericDataType)) then
   begin
     Result := TABSExprNodeConst.Create;
     Result.Value.Assign(Token.ParamValue);
     // get next token
     GetNextToken;
    end
 else
  Result := nil;
end;//ParseUnsignedNumericLiteral


//------------------------------------------------------------------------------
// parses <column reference>
//------------------------------------------------------------------------------
function TABSExpression.ParseColumnReference: TABSExprNode;
var
  FieldName, TableName: string;
  IsResWord: boolean;
begin
 Result := nil;
 //InitDataValue(Value);
 if ((Token.TokenType = tktString) or
     (Token.TokenType = tktBracketedString) or
     (Token.TokenType = tktReservedWord)) then
  begin
   //SavedTokenNo := FLex.GetCurrentTokenNo;
   FieldName := Token.Text;
   TableName := '';
   IsResWord := Token.ReservedWord <> rwNone;
   // get next token
   GetNextToken;
   // table.field?
   if (Token.TokenType = tktDot) then
    begin
     TableName := FieldName;
     GetNextToken;
     FieldName := Token.Text;
     // get next token
     GetNextToken;
    end;
   // function ?
   if (Token.TokenType <> tktLeftParenthesis) and (not IsResWord) then
     Result := TABSExprNodeField.Create(LCursor, FieldName, TableName);
  end;
 //FinalizeDataValue(Value);
end;//ParseColumnReference


//------------------------------------------------------------------------------
// parses <set function specification>
//------------------------------------------------------------------------------
function TABSExpression.ParseSetFunctionSpecification: TABSExprNode;
var
  SavedTokenNo: integer;        // saved/restored information
begin
{
<set function specification> ::=
      COUNT <left paren> <asterisk> <right paren>
    | <general set function>
}
 Result := nil;
 SaveState(SavedTokenNo);
 if IsReservedWord(Token, rwCOUNT) then
  begin
   // get next token
   GetNextToken;
   // '('
   if Token.TokenType = tktLeftParenthesis then
    begin
     // get next token
     GetNextToken;
     // '*'
     if Token.Text='*' then
      begin
       // get next token
       GetNextToken;
       // ')'
       if Token.TokenType <> tktRightParenthesis then
       raise EABSException.Create(30073, ErrorGOtherTokenExpected,
                             [')', Token.Text, Token.LineNum, Token.ColumnNum]);
       // get next token
       GetNextToken;
       Result := TABSExprNodeAggregated.Create(doCOUNTALL);
      end;
    end;
  end;
 if Result = nil then
  begin
   RestoreState(SavedTokenNo);
   Result := ParseGeneralSetFunction;
  end;
end;//ParseSetFunctionSpecification


//------------------------------------------------------------------------------
// parses <general set function>
//------------------------------------------------------------------------------
function TABSExpression.ParseGeneralSetFunction: TABSExprNode;
var
  SavedTokenNo: integer;        // saved/restored information
  expr: TABSExprNode;
  distinct: boolean;
  oper: TABSDataOperator;
begin
{
<general set function> ::=
      <set function type>
          <left paren> [ <set quantifier> ] <value expression> <right paren>
}
 Result := nil;
 SaveState(SavedTokenNo);

 oper := doUNDEFINED;
 if IsReservedWord(Token, rwCOUNT) then
  oper:=doCOUNT
 else if IsReservedWord(Token, rwAVG) then
  oper:=doAVG
 else if IsReservedWord(Token, rwMAX) then
  oper:=doMAX
 else if IsReservedWord(Token, rwMIN) then
  oper:=doMIN
 else if IsReservedWord(Token, rwSUM) then
  oper:=doSUM;

 if oper = doUNDEFINED then Exit;

 // get next token
 GetNextToken;

 // DISTINCT
 distinct := IsReservedWord(Token, rwDISTINCT);
 if distinct then GetNextToken;

 // '('
 if Token.TokenType <> tktLeftParenthesis then
   raise EABSException.Create(30074, ErrorGOtherTokenExpected,
                            ['(', Token.Text, Token.LineNum, Token.ColumnNum]);
 // get next token
 GetNextToken;

 // EXPRESSION
 expr := ParseValueExpression;
 if expr = nil then
   raise EABSException.Create(30102, ErrorGArgumentExpected,
            [GetOperatorName(oper), Token.LineNum, Token.ColumnNum]);

 // ')'
 if Token.TokenType <> tktRightParenthesis then
  begin
   expr.Free;
   raise EABSException.Create(30075, ErrorGOtherTokenExpected,
                            [')', Token.Text, Token.LineNum, Token.ColumnNum]);
  end;
 // get next token
 GetNextToken;

 // Return GroupFunctionNode
 Result := TABSExprNodeAggregated.Create(oper, distinct, expr);
end;//ParseGeneralSetFunction



//------------------------------------------------------------------------------
// parses <cast specification>
//------------------------------------------------------------------------------
function TABSExpression.ParseCastSpecification: TABSExprNode;
var
  Operand, CastSize: TABSExprNode;
  fType: TABSAdvancedFieldType;
begin
{
<cast specification> ::=
    CAST <left paren> <cast operand> [as | , ]
        <cast target>[(Size)] <right paren>
}
 //raise Exception.Create('Not supported yet');
 Result := nil;
 CastSize := nil;
 if IsReservedWord(Token, rwCAST) then
  begin
   // get next token
   GetNextToken;
   // '('
   if Token.TokenType = tktLeftParenthesis then
    begin
     // get next token
     GetNextToken;
     // Operand
     Operand := ParseCastOperand;
     if Operand <> nil then
      begin
       if (Token.TokenType = tktComma) or
          IsReservedWord(Token, rwAS) then
         GetNextToken;

         // Get Field Type
         fType := GetFieldType(Token.Text);
         // If correct fType
         if IsConvertableFieldType(fType) then
          begin
           // get next token
           GetNextToken;

           // Get Field Size
           if Token.TokenType = tktLeftParenthesis then
             begin
               GetNextToken;
               CastSize := ParseNumericValueExpression;
               GetNextToken;
               // ')'
               if Token.TokenType <> tktRightParenthesis then
                begin
                 if (CastSize <> nil) then CastSize.Free;
                 Operand.Free;
                 raise EABSException.Create(30465, ErrorGOtherTokenExpected,
                              [')', Token.Text,Token.LineNum,Token.ColumnNum]);
                end;
               //Operand.t
             end;

           // ')'
           if Token.TokenType <> tktRightParenthesis then
            begin
              Operand.Free;
              raise EABSException.Create(30076, ErrorGOtherTokenExpected,
                              [')', Token.Text,Token.LineNum,Token.ColumnNum]);
            end;
           Result := TABSExprNodeCast.Create(Operand, fType, CastSize);
           // get next token
           GetNextToken;
          end
         else
          begin
           Operand.Free;
           raise EABSException.Create(30077, ErrorGNotApplicableCastType,
                                      [AftToStr(fType)]);
          end;
        end;
      end;
    end;
end;//ParseCastSpecification


//------------------------------------------------------------------------------
// parses <cast operand>
//------------------------------------------------------------------------------
function TABSExpression.ParseCastOperand: TABSExprNode;
var
  SavedTokenNo: integer;
begin
{
<cast operand> ::=
      <value expression>
    | NULL
}
 // NULL
 SaveState(SavedTokenNo);

 Result := ParseNullConst;
 GetNextToken;
 if (Result = nil) then
  begin
    // restore original state
    RestoreState(SavedTokenNo);
    Result := ParseValueExpression;
  end;
end;//ParseCastOperand


//------------------------------------------------------------------------------
// parses <numeric value function>
//------------------------------------------------------------------------------
function TABSExpression.ParseNumericValueFunction: TABSExprNode;
var
  SavedTokenNo: integer;        // saved/restored information
begin
{
<numeric value function> ::=
      <position expression>
    | <extract expression>
    | <length expression>
    | <LastAutoInc expression>
    | <RowNum expression>
    | <math expression>
}
 // save current state
 SaveState(SavedTokenNo);
 Result := ParsePositionExpression;
 if (Result = nil) then
  begin
    // restore original state
    RestoreState(SavedTokenNo);
    Result := ParseExtractExpression;
  end;
 if (Result = nil) then
  begin
    // restore original state
    RestoreState(SavedTokenNo);
    Result := ParseLengthExpression;
  end;
 if (Result = nil) then
  begin
    // restore original state
    RestoreState(SavedTokenNo);
    Result := ParseLastAutoIncExpression;
  end;
 if (Result = nil) then
  begin
    // restore original state
    RestoreState(SavedTokenNo);
    Result := ParseRowNumExpression;
  end;
 if (Result = nil) then
  begin
    // restore original state
    RestoreState(SavedTokenNo);
    Result := ParseMathExpression;
  end;
end;//ParseNumericValueFunction


//------------------------------------------------------------------------------
// parses <position expression>
//------------------------------------------------------------------------------
function TABSExpression.ParsePositionExpression: TABSExprNode;
var
  arg1, arg2: TABSExprNode;
begin
 if IsReservedWord(Token, rwPOSITION) or IsReservedWord(Token, rwPOS) then
  begin
    // get next token
    GetNextToken;

    // '('
    if Token.TokenType <> tktLeftParenthesis then
     raise EABSException.Create(30078, ErrorGOtherTokenExpected,
                             ['(', Token.Text, Token.LineNum, Token.ColumnNum]);
    // get next token
    GetNextToken;

    // arg
    arg1 := ParseCharacterValueExpression;
    if arg1 = nil then
     raise EABSException.Create(30079, ErrorGArgumentExpected,
                                       ['POS', Token.LineNum, Token.ColumnNum]);

    // ',' or 'in'
    if (Token.TokenType <> tktComma) and not IsReservedWord(Token, rwIN) then
     begin
       arg1.Free;
       raise EABSException.Create(30080, ErrorGOtherTokenExpected,
                [','' or ''IN', Token.Text, Token.LineNum, Token.ColumnNum]);
     end;
    // get next token
    GetNextToken;

    arg2 := ParseCharacterValueExpression;
    if arg2 = nil then
     begin
      arg1.Free;
      raise EABSException.Create(30081, ErrorGArgumentExpected,
                                       ['POS', Token.LineNum, Token.ColumnNum]);
     end;

    Result := TABSExprNodeStringFunction.Create(doPOS, arg1, arg2);

    // ')'
    if Token.TokenType <> tktRightParenthesis then
     begin
      Result.Free;
      raise EABSException.Create(30082, ErrorGOtherTokenExpected,
                             [')', Token.Text, Token.LineNum, Token.ColumnNum]);
     end;
    // get next token
    GetNextToken;
  end
 else
  Result := nil;
end;//ParsePositionExpression


//------------------------------------------------------------------------------
// parses <extract expression>
//------------------------------------------------------------------------------
function TABSExpression.ParseExtractExpression: TABSExprNode;
var
  ExtractField: TABSDateTimeExtractField;
  ExtractSource: TABSExprNode;
begin
 if IsReservedWord(Token, rwEXTRACT) then
  begin
    // get next token
    GetNextToken;

    // '('
    if Token.TokenType <> tktLeftParenthesis then
     raise EABSException.Create(20277, ErrorGOtherTokenExpected,
                             ['(', Token.Text, Token.LineNum, Token.ColumnNum]);
    // get next token
    GetNextToken;

    // datetime extract field (YEAR, MONTH, ...)
    if (Token.TokenType <> tktReservedWord) then
     raise EABSException.Create(20278, ErrorGOtherTokenExpected,
                                       ['YEAR or MONTH or DAY or HOUR or MINUTE or SECOND', Token.LineNum, Token.ColumnNum]);
    case Token.ReservedWord of
     rwYEAR: ExtractField := dtefYear;
     rwMONTH: ExtractField := dtefMonth;
     rwDAY: ExtractField := dtefDay;
     rwHOUR: ExtractField := dtefHour;
     rwMINUTE: ExtractField := dtefMinute;
     rwSECOND: ExtractField := dtefSecond;
    else
     raise EABSException.Create(20279, ErrorGOtherTokenExpected,
                                       ['YEAR or MONTH or DAY or HOUR or MINUTE or SECOND', Token.LineNum, Token.ColumnNum]);
    end;
    // get next token
    GetNextToken;

    // FROM
    if not IsReservedWord(Token, rwFROM) then
      raise EABSException.Create(20280, ErrorGOtherTokenExpected,
              ['FROM', Token.Text, Token.LineNum, Token.ColumnNum]);
    // get next token
    GetNextToken;

    ExtractSource := ParseValueExpressionPrimary;
    if ExtractSource = nil then
      raise EABSException.Create(20281, ErrorGArgumentExpected,
                                       ['EXTRACT (... FROM ...)', Token.LineNum, Token.ColumnNum]);

    Result := TABSExprNodeDateFunction.Create(doEXTRACT, ExtractField, ExtractSource);

    // ')'
    if Token.TokenType <> tktRightParenthesis then
     begin
      Result.Free;
      raise EABSException.Create(20282, ErrorGOtherTokenExpected,
                             [')', Token.Text, Token.LineNum, Token.ColumnNum]);
     end;
    // get next token
    GetNextToken;
  end
 else
  Result := nil;
end;// ParseExtractExpression


//------------------------------------------------------------------------------
// parses <length expression>
//------------------------------------------------------------------------------
function TABSExpression.ParseLengthExpression: TABSExprNode;
begin
 if IsReservedWord(Token, rwLENGTH) then
  begin
    // get next token
    GetNextToken;

    // '('
    if Token.TokenType <> tktLeftParenthesis then
     raise EABSException.Create(30083, ErrorGOtherTokenExpected,
                             ['(', Token.Text, Token.LineNum, Token.ColumnNum]);
    // get next token
    GetNextToken;

    // argument
    Result := ParseStringValueExpression;
    if result = nil then
      raise EABSException.Create(30085, ErrorGArgumentExpected,
                                     ['LENGTH', Token.LineNum, Token.ColumnNum]);

    Result := TABSExprNodeStringFunction.Create(doLENGTH, Result);

    // ')'
    if Token.TokenType <> tktRightParenthesis then
     begin
      Result.Free;
      raise EABSException.Create(30084, ErrorGOtherTokenExpected,
                             [')', Token.Text, Token.LineNum, Token.ColumnNum]);
     end;

    // get next token
    GetNextToken;
  end
 else
  Result := nil;
end;//ParseLengthExpression


//------------------------------------------------------------------------------
// parses <LastAutoInc expression>
//------------------------------------------------------------------------------
function TABSExpression.ParseLastAutoIncExpression: TABSExprNode;
var
  TableName, ColumnName: string;
  MemoryWord: Boolean;
begin

 //  LastAutoinc(TableName, ColumnName)

 if IsReservedWord(Token, rwLAST_AUTOINC) then
  begin
    TableName := '';
    ColumnName := '';
    MemoryWord := False;
    
    // get next token
    GetNextToken;

    // '('
    if (Token.TokenType <> tktLeftParenthesis) then
      raise EABSException.Create(30086, ErrorGOtherTokenExpected,
                             ['(', Token.Text, Token.LineNum, Token.ColumnNum]);
      // table_name
    GetNextToken;
      TableName := Token.Text;


    GetNextToken;

    // MEMORY
    if (IsReservedWord(Token, rwMEMORY)) then
     begin
       MemoryWord := True;
      GetNextToken;
     end;

    // ','
    if (Token.TokenType <> tktComma) then
      raise EABSException.Create(30334, ErrorGOtherTokenExpected,
                           [',', Token.Text, Token.LineNum, Token.ColumnNum]);


    // column_name
        GetNextToken;
    ColumnName := Token.Text;

      // ')'
    GetNextToken;
      if Token.TokenType <> tktRightParenthesis then
        raise EABSException.Create(30089, ErrorGOtherTokenExpected,
                             [')', Token.Text, Token.LineNum, Token.ColumnNum]);


    // get next token
    GetNextToken;

    Result := TABSExprNodeSystem.Create(doLASTAUTOINC, FSessionName, FDatabaseName,
                                        TableName, ColumnName, (InMemory or MemoryWord));
  end
 else
  Result := nil;
end;//ParseLastAutoIncExpression


//------------------------------------------------------------------------------
// parses <RowNum expression>
//------------------------------------------------------------------------------
function TABSExpression.ParseRowNumExpression: TABSExprNode;
begin
 if IsReservedWord(Token, rwROWNUM) then
  begin
    // get next token
    GetNextToken;
    Result := TABSExprNodeSystem.Create(doROWNUM, '', '', '',
                                        '', false);
  end
 else
  Result := nil;
end;// ParseRowNumExpression


//------------------------------------------------------------------------------
// parses <x(y) expression>
//------------------------------------------------------------------------------
function TABSExpression.ParseSingleArgMathExpression(rw: TReservedWord;
                            op: TABSDataOperator; OpText: string): TABSExprNode;
begin
 if IsReservedWord(Token, rw) then
  begin
    // get next token
    GetNextToken;

    // '('
    if Token.TokenType <> tktLeftParenthesis then
     raise EABSException.Create(20261, ErrorGOtherTokenExpected,
                             ['(', Token.Text, Token.LineNum, Token.ColumnNum]);
    // get next token
    GetNextToken;

    // argument
    Result := ParseNumericValueExpression;
    if result = nil then
      raise EABSException.Create(20262, ErrorGArgumentExpected,
                                     [OpText, Token.LineNum, Token.ColumnNum]);

    Result := TABSExprNodeArithmetic.Create(op, Result);

    // ')'
    if Token.TokenType <> tktRightParenthesis then
     begin
      Result.Free;
      raise EABSException.Create(20263, ErrorGOtherTokenExpected,
                             [')', Token.Text, Token.LineNum, Token.ColumnNum]);
     end;

    // get next token
    GetNextToken;
  end
 else
  Result := nil;
end;// ParseSingleArgMathExpression


//------------------------------------------------------------------------------
// parses <ABS expression>
//------------------------------------------------------------------------------
function TABSExpression.ParseABSExpression: TABSExprNode;
begin
  Result := ParseSingleArgMathExpression(rwABS,doABS,'ABS');
end;// ParseABSExpression


//------------------------------------------------------------------------------
// parses <ACOS expression>
//------------------------------------------------------------------------------
function TABSExpression.ParseACOSExpression: TABSExprNode;
begin
  Result := ParseSingleArgMathExpression(rwACOS,doACOS,'ACOS');
end;// ParseACOSExpression


//------------------------------------------------------------------------------
// parses <ASIN expression>
//------------------------------------------------------------------------------
function TABSExpression.ParseASINExpression: TABSExprNode;
begin
  Result := ParseSingleArgMathExpression(rwASIN,doASIN,'ASIN');
end;// ParseASINExpression


//------------------------------------------------------------------------------
// parses <ATAN expression>
//------------------------------------------------------------------------------
function TABSExpression.ParseATANExpression: TABSExprNode;
begin
  Result := ParseSingleArgMathExpression(rwATAN,doATAN,'ATAN');
end;// ParseATANExpression


//------------------------------------------------------------------------------
// parses <CEIL expression>
//------------------------------------------------------------------------------
function TABSExpression.ParseCEILExpression: TABSExprNode;
begin
  Result := ParseSingleArgMathExpression(rwCEIL,doCEIL,'CEIL');
end;// ParseCEILExpression


//------------------------------------------------------------------------------
// parses <COS expression>
//------------------------------------------------------------------------------
function TABSExpression.ParseCOSExpression: TABSExprNode;
begin
  Result := ParseSingleArgMathExpression(rwCOS,doCOS,'COS');
end;// ParseCOSExpression


//------------------------------------------------------------------------------
// parses <EXP expression>
//------------------------------------------------------------------------------
function TABSExpression.ParseEXPExpression: TABSExprNode;
begin
  Result := ParseSingleArgMathExpression(rwEXP,doEXP,'EXP');
end;// ParseEXPExpression


//------------------------------------------------------------------------------
// parses <FLOOR expression>
//------------------------------------------------------------------------------
function TABSExpression.ParseFLOORExpression: TABSExprNode;
begin
  Result := ParseSingleArgMathExpression(rwFLOOR,doFLOOR,'FLOOR');
end;// ParseFLOORExpression


//------------------------------------------------------------------------------
// parses <LOG expression>
//------------------------------------------------------------------------------
function TABSExpression.ParseLOGExpression: TABSExprNode;
begin
  Result := ParseSingleArgMathExpression(rwLOG,doLOG,'LOG');
end;// ParseLOGExpression


//------------------------------------------------------------------------------
// parses <POWER expression>
//------------------------------------------------------------------------------
function TABSExpression.ParsePOWERExpression: TABSExprNode;
var
  arg1,arg2: TABSExprNode;
begin
 if IsReservedWord(Token, rwPOWER) then
  begin
    // get next token
    GetNextToken;

    // '('
    if Token.TokenType <> tktLeftParenthesis then
     raise EABSException.Create(20264, ErrorGOtherTokenExpected,
                             ['(', Token.Text, Token.LineNum, Token.ColumnNum]);
    // get next token
    GetNextToken;

    // argument1
    arg1 := ParseNumericValueExpression;
    if arg1 = nil then
      raise EABSException.Create(20265, ErrorGArgumentExpected,
                                     ['POWER', Token.LineNum, Token.ColumnNum]);

    // parse ','
    if (Token.TokenType <> tktComma) then
      raise EABSException.Create(20267, ErrorACommaExpected,
                                     [Token.LineNum, Token.ColumnNum]);
    // get next token
    GetNextToken;

    // argument2
    arg2 := ParseNumericValueExpression;
    if arg2 = nil then
      raise EABSException.Create(20268, ErrorGArgumentExpected,
                                     ['POWER', Token.LineNum, Token.ColumnNum]);

    Result := TABSExprNodeArithmetic.Create(doPOWER, arg1,arg2);

    // ')'
    if Token.TokenType <> tktRightParenthesis then
     begin
      Result.Free;
      raise EABSException.Create(20266, ErrorGOtherTokenExpected,
                             [')', Token.Text, Token.LineNum, Token.ColumnNum]);
     end;

    // get next token
    GetNextToken;
  end
 else
  Result := nil;
end;// ParsePOWERExpression


//------------------------------------------------------------------------------
// parses <RAND expression>
//------------------------------------------------------------------------------
function TABSExpression.ParseRANDExpression: TABSExprNode;
begin
  Result := ParseSingleArgMathExpression(rwRAND,doRAND,'RAND');
end;// ParseRANDExpression


//------------------------------------------------------------------------------
// parses <ROUND expression>
//------------------------------------------------------------------------------
function TABSExpression.ParseROUNDExpression: TABSExprNode;
var
  arg1,arg2,arg3: TABSExprNode;
begin
//  Result := ParseSingleArgMathExpression(rwROUND,doROUND,'ROUND');
 if IsReservedWord(Token, rwROUND) then
  begin
    // get next token
    GetNextToken;

    // '('
    if Token.TokenType <> tktLeftParenthesis then
     raise EABSException.Create(30502, ErrorGOtherTokenExpected,
                             ['(', Token.Text, Token.LineNum, Token.ColumnNum]);
    // get next token
    GetNextToken;

    // argument1
    arg1 := ParseNumericValueExpression;
    if arg1 = nil then
      raise EABSException.Create(30503, ErrorGArgumentExpected,
                                     ['ROUND', Token.LineNum, Token.ColumnNum]);

    arg2 := nil;
    arg3 := nil;
    // parse ','
    if (Token.TokenType = tktComma) then
      begin
        // get next token
        GetNextToken;

        // argument2
        arg2 := ParseNumericValueExpression;
        if arg2 = nil then
          raise EABSException.Create(30504, ErrorGArgumentExpected,
                                         ['ROUND', Token.LineNum, Token.ColumnNum]);

        // parse ','
        if (Token.TokenType = tktComma) then
          begin
            // get next token
            GetNextToken;

            if Token.TokenType <> tktReservedWord then
              raise EABSException.Create(30505, ErrorGArgumentExpected,
                                             ['ROUND', Token.LineNum, Token.ColumnNum]);
            arg3 := TABSExprNodeConst.Create;
            arg3.Value.AsInteger := Integer(Token.ReservedWord);
            GetNextToken;
          end;
      end;

    if (arg2 = nil) then
      Result := TABSExprNodeArithmetic.Create(doROUND, arg1)
    else
    if (arg3 = nil) then
      Result := TABSExprNodeArithmetic.Create(doROUND, arg1,arg2)
    else
      Result := TABSExprNodeArithmetic.Create(doROUND, arg1,arg2,arg3);

    // ')'
    if Token.TokenType <> tktRightParenthesis then
     begin
      Result.Free;
      raise EABSException.Create(30506, ErrorGOtherTokenExpected,
                             [')', Token.Text, Token.LineNum, Token.ColumnNum]);
     end;

    // get next token
    GetNextToken;
  end
 else
  Result := nil;
end;// ParseROUNDExpression


//------------------------------------------------------------------------------
// parses <SIGN expression>
//------------------------------------------------------------------------------
function TABSExpression.ParseSIGNExpression: TABSExprNode;
begin
  Result := ParseSingleArgMathExpression(rwSIGN,doSIGN,'SIGN');
end;// ParseSIGNExpression


//------------------------------------------------------------------------------
// parses <SIN expression>
//------------------------------------------------------------------------------
function TABSExpression.ParseSINExpression: TABSExprNode;
begin
  Result := ParseSingleArgMathExpression(rwSIN,doSIN,'SIN');
end;// ParseSINExpression


//------------------------------------------------------------------------------
// parses <SQR expression>
//------------------------------------------------------------------------------
function TABSExpression.ParseSQRExpression: TABSExprNode;
begin
  Result := ParseSingleArgMathExpression(rwSQR,doSQR,'SQR');
end;// ParseSQRExpression


//------------------------------------------------------------------------------
// parses <SQRT expression>
//------------------------------------------------------------------------------
function TABSExpression.ParseSQRTExpression: TABSExprNode;
begin
  Result := ParseSingleArgMathExpression(rwSQRT,doSQRT,'SQRT');
end;// ParseSQRTExpression


//------------------------------------------------------------------------------
// parses <math expression>
//------------------------------------------------------------------------------
function TABSExpression.ParseMathExpression: TABSExprNode;
var
  SavedTokenNo: integer;        // saved/restored information
begin
{
<math expression> ::=
      <ABS expression>
    | <ACOS expression>
    | <ASIN expression>
    | <ATAN expression>
    | <CEIL expression>
    | <COS expression>
    | <EXP expression>
    | <FLOOR expression>
    | <LOG expression>
    | <POWER expression>
    | <RAND expression>
    | <ROUND expression>
    | <SIGN expression>
    | <SIN expression>
    | <SQR expression>
    | <SQRT expression>
}
 // save current state
 SaveState(SavedTokenNo);
 Result := ParseABSExpression;
 if (Result = nil) then
  begin
    RestoreState(SavedTokenNo);
    Result := ParseACOSExpression;
  end;
 if (Result = nil) then
  begin
    RestoreState(SavedTokenNo);
    Result := ParseASINExpression;
  end;
 if (Result = nil) then
  begin
    RestoreState(SavedTokenNo);
    Result := ParseATANExpression;
  end;
 if (Result = nil) then
  begin
    RestoreState(SavedTokenNo);
    Result := ParseCEILExpression;
  end;
 if (Result = nil) then
  begin
    RestoreState(SavedTokenNo);
    Result := ParseCOSExpression;
  end;
 if (Result = nil) then
  begin
    RestoreState(SavedTokenNo);
    Result := ParseEXPExpression;
  end;
 if (Result = nil) then
  begin
    RestoreState(SavedTokenNo);
    Result := ParseFLOORExpression;
  end;
 if (Result = nil) then
  begin
    RestoreState(SavedTokenNo);
    Result := ParseLOGExpression;
  end;
 if (Result = nil) then
  begin
    RestoreState(SavedTokenNo);
    Result := ParsePOWERExpression;
  end;
 if (Result = nil) then
  begin
    RestoreState(SavedTokenNo);
    Result := ParseRANDExpression;
  end;
 if (Result = nil) then
  begin
    RestoreState(SavedTokenNo);
    Result := ParseROUNDExpression;
  end;
 if (Result = nil) then
  begin
    RestoreState(SavedTokenNo);
    Result := ParseSIGNExpression;
  end;
 if (Result = nil) then
  begin
    RestoreState(SavedTokenNo);
    Result := ParseSINExpression;
  end;
 if (Result = nil) then
  begin
    RestoreState(SavedTokenNo);
    Result := ParseSQRExpression;
  end;
 if (Result = nil) then
  begin
    RestoreState(SavedTokenNo);
    Result := ParseSQRTExpression;
  end;
end;// ParseMathExpression


//------------------------------------------------------------------------------
// parses <string value expression>
//------------------------------------------------------------------------------
function TABSExpression.ParseStringValueExpression: TABSExprNode;
begin
{
<string value expression> ::=
      <character value expression>
    | <bit value expression>
}
  Result := ParseCharacterValueExpression;
end;//ParseStringValueExpression


//------------------------------------------------------------------------------
// parses <character value expression>
//------------------------------------------------------------------------------
function TABSExpression.ParseCharacterValueExpression: TABSExprNode;
var
  SavedTokenNo: integer;        // saved/restored information
begin
{
<character value expression> ::=
      <concatenation>
    | <character factor>
}
  // save current state
  SaveState(SavedTokenNo);
  Result := ParseConcatination;
  if Result = nil then
   begin
    RestoreState(SavedTokenNo);
    Result := ParseCharacterFactor;
   end;
end;//ParseCharacterValueExpression


//------------------------------------------------------------------------------
// parses <concatenation>
//------------------------------------------------------------------------------
function TABSExpression.ParseConcatination: TABSExprNode;
var
  left, right: TABSExprNode;
begin
{
<concatenation> ::=
    <character value expression> <concatenation operator>
        <character factor>
}
  Result := nil;
  left := ParseCharacterFactor;
  //left := ParseCharacterValueExpression;
  if left <> nil then
   if (Token.Text = '||') or (Token.Text = '+') then
    begin
     Result := TABSExprNodeStringFunction.Create(doConcat, left);
     while (Token.Text = '||') or (Token.Text = '+') do
      begin
       GetNextToken;
       right := ParseCharacterFactor;
       if right <> nil then
        Result.Children.add(right);
      end;
    end
   else
    left.Free;
end;//ParseConcatination


//------------------------------------------------------------------------------
// parses <character factor>
//------------------------------------------------------------------------------
function TABSExpression.ParseCharacterFactor: TABSExprNode;
begin
{
<character factor> ::=
    <character primary> [ <collate clause> ]
}
  Result := ParseCharacterPrimary;
end;//ParseCharacterFactor


//------------------------------------------------------------------------------
// parses <character primary>
//------------------------------------------------------------------------------
function TABSExpression.ParseCharacterPrimary: TABSExprNode;
begin
{
<character primary> ::=
      <value expression primary>
    | <string value function>
}
  Result := ParseValueExpressionPrimary;

  if Result = nil then
    Result := ParseStringValueFunction;

  if Result = nil then
    Result := ParseDatetimeValueExpression;
end;//ParseCharacterPrimary


//------------------------------------------------------------------------------
// parses <general literal>
//------------------------------------------------------------------------------
function TABSExpression.ParseGeneralLiteral: TABSExprNode;
var
  SavedTokenNo: integer;        // saved/restored information
begin
{
<general literal> ::=
      <character string literal>
    | <national character string literal>
    | <bit string literal>
    | <hex string literal>
    | <datetime literal>
    | <interval literal>
}
  // save current state
  SaveState(SavedTokenNo);
  Result := ParseCharacterStringLiteral;
  if Result = nil then
   begin
    RestoreState(SavedTokenNo);
    Result := ParseDateTimeLiteral;
   end;
end;//ParseGeneralLiteral


//------------------------------------------------------------------------------
// parses <character string literal>
//------------------------------------------------------------------------------
function TABSExpression.ParseCharacterStringLiteral: TABSExprNode;
begin
(*
<character string literal> ::=
    [ <introducer><character set specification> ]
    <quote> [ <character representation>... ] <quote>
      [ { <separator>... <quote> [ <character representation>... ] <quote> }...]
*)
 Result := nil;

 if (Token.TokenType = tktQuotedString) then
  begin
   Result := TABSExprNodeConst.Create;
{$IFDEF D12H}
   Result.Value.AsWideString := Token.Text;
   Result.Value.TryToReduceWideStringToAnsiString;
{$ELSE}
   Result.Value.AsAnsiString := Token.Text;
{$ENDIF}
   // get next token
   GetNextToken;
  end
 else if (Token.TokenType = tktParameter) and
          ((Token.ParamValue.IsStringDataType) or
           (Token.ParamValue.IsBlobDataType)) then
  begin
    Result := TABSExprNodeConst.Create;
    Result.Value.Assign(Token.ParamValue);
    // get next token
    GetNextToken;
  end

end;//ParseCharacterStringLiteral


//------------------------------------------------------------------------------
// parses <datetime literal>
//------------------------------------------------------------------------------
function TABSExpression.ParseDateTimeLiteral: TABSExprNode;
var
  SavedTokenNo: integer;        // saved/restored information
begin
{
<datetime literal> ::=
      <date literal>
    | <time literal>
    | <timestamp literal>
}
  // save current state
  SaveState(SavedTokenNo);
  Result := ParseDateLiteral;
  if Result = nil then
   begin
    RestoreState(SavedTokenNo);
    Result := ParseTimeLiteral;
   end;
  if Result = nil then
   begin
    RestoreState(SavedTokenNo);
    Result := ParseTimeStampLiteral;
   end;
end;// ParseDateTimeLiteral


//------------------------------------------------------------------------------
// parses <date literal>
//------------------------------------------------------------------------------
function TABSExpression.ParseDateLiteral: TABSExprNode;
begin
  if IsReservedWord(Token, rwDATE) then
    begin
      GetNextToken;
      Result := ParseDateString;
    end
  else
    Result := nil;
end;// ParseDateLiteral


//------------------------------------------------------------------------------
// parses <date string>
//------------------------------------------------------------------------------
function TABSExpression.ParseDateString: TABSExprNode;
var
  DateValue: TDateTime;
begin
  if (Token.TokenType <> tktQuotedString) then
    raise EABSException.Create(20283, ErrorADateStringExpected,
                             [Token.Text, Token.LineNum, Token.ColumnNum]);
  if (not ISODateStrToDate(Token.Text, DateValue)) then
    raise EABSException.Create(20284, ErrorGOtherTokenExpected,
                             ['YYYY-MM-DD',Token.Text, Token.LineNum, Token.ColumnNum]);
  Result := TABSExprNodeConst.Create;
  Result.Value.AsTDate := DateValue;
  GetNextToken;
end;// ParseDateString


//------------------------------------------------------------------------------
// parses <time literal>
//------------------------------------------------------------------------------
function TABSExpression.ParseTimeLiteral: TABSExprNode;
begin
  if IsReservedWord(Token, rwTIME) then
    begin
      GetNextToken;
      Result := ParseTimeString;
    end
  else
    Result := nil;
end;// ParseTimeLiteral


//------------------------------------------------------------------------------
// parses <time string>
//------------------------------------------------------------------------------
function TABSExpression.ParseTimeString: TABSExprNode;
var
  TimeValue: TDateTime;
begin
  if (Token.TokenType <> tktQuotedString) then
    raise EABSException.Create(20285, ErrorATimeStringExpected,
                             [Token.Text, Token.LineNum, Token.ColumnNum]);
  if (not ISOTimeStrToTime(Token.Text, TimeValue)) then
    raise EABSException.Create(20286, ErrorGOtherTokenExpected,
                             ['HH:MM:SS',Token.Text, Token.LineNum, Token.ColumnNum]);
  Result := TABSExprNodeConst.Create;
  Result.Value.AsTTime := TimeValue;
  GetNextToken;
end;// ParseTimeString


//------------------------------------------------------------------------------
// parses <timestamp literal>
//------------------------------------------------------------------------------
function TABSExpression.ParseTimeStampLiteral: TABSExprNode;
begin
  if IsReservedWord(Token, rwTIMESTAMP) then
    begin
      GetNextToken;
      Result := ParseTimeStampString;
    end
  else
    Result := nil;
end;// ParseTimeStampLiteral


//------------------------------------------------------------------------------
// parses <timestamp string>
//------------------------------------------------------------------------------
function TABSExpression.ParseTimeStampString: TABSExprNode;
var
  DateTimeValue: TDateTime;
begin
  if (Token.TokenType <> tktQuotedString) then
    raise EABSException.Create(20287, ErrorATimeStringExpected,
                             [Token.Text, Token.LineNum, Token.ColumnNum]);
  if (not ISOTimeStampStrToDateTime(Token.Text, DateTimeValue)) then
    raise EABSException.Create(20288, ErrorGOtherTokenExpected,
                             ['YYYY-MM-DD',Token.Text, Token.LineNum, Token.ColumnNum]);
  Result := TABSExprNodeConst.Create;
  Result.Value.AsTDateTime := DateTimeValue;
  GetNextToken;
end;// ParseTimeStampString


//------------------------------------------------------------------------------
// parses <string value function>
//------------------------------------------------------------------------------
function TABSExpression.ParseStringValueFunction: TABSExprNode;
begin
{
<string value function> ::=
      <character value function>
    | <bit value function>
}
  Result := ParseCharacterValueFunction;
end;//ParseStringValueFunction


//------------------------------------------------------------------------------
// parses <character value function>
//------------------------------------------------------------------------------
function TABSExpression.ParseCharacterValueFunction: TABSExprNode;
var
  arg1,arg2,arg3: TABSExprNode;
  op: TABSDataOperator;
begin
{
<character value function> ::=
      <character substring function>
    | <fold>
    | <form-of-use conversion>
    | <character translation>
    | <trim function>
}
  Result := nil;
  op := doUNDEFINED;
  if IsReservedWord(Token, rwUPPER) then op:=doUPPER
  else if IsReservedWord(Token, rwLOWER) then op:=doLOWER
  else if IsReservedWord(Token, rwTRIM) then op:=doTRIM
  else if IsReservedWord(Token, rwLTRIM) then op:=doLTRIM
  else if IsReservedWord(Token, rwRTRIM) then op:=doRTRIM
  else if IsReservedWord(Token, rwSUBSTRING) then op:=doSUBSTRING
  else if IsReservedWord(Token, rwTOSTRING) then op:=doTOSTRING
  ;
  if op = doUNDEFINED then Exit;
  // get next token
  GetNextToken;

  // Functions with 1 agrument
  if op in [doUPPER, doLOWER, doTRIM, doLTRIM, doRTRIM] then
   begin
    // '('
    if Token.TokenType <> tktLeftParenthesis then
      raise EABSException.Create(30090, ErrorGOtherTokenExpected,
                             ['(', Token.Text, Token.LineNum, Token.ColumnNum]);
    // get next token
    GetNextToken;

    // arg1
    arg1 := ParseCharacterValueExpression;

    // ')'
    if Token.TokenType <> tktRightParenthesis then
      raise EABSException.Create(30091, ErrorGOtherTokenExpected,
                             [')', Token.Text, Token.LineNum, Token.ColumnNum]);
    // get next token
    GetNextToken;

    if arg1 = nil then
      raise EABSException.Create(30092, ErrorGStringArgumentExpected,
             [GetOperatorName(op), Token.Text, Token.LineNum, Token.ColumnNum]);
    Result := TABSExprNodeStringFunction.Create(op, arg1);
   end
  else
  // Functions with 2 agruments
  if op = doTOSTRING then
   begin
    // '('
    if Token.TokenType <> tktLeftParenthesis then
      raise EABSException.Create(30093, ErrorGOtherTokenExpected,
                             ['(', Token.Text, Token.LineNum, Token.ColumnNum]);
    // get next token
    GetNextToken;

    //TOSTRING(str, format)
    // string
    arg1 := ParseValueExpression;
    if arg1 = nil then
     raise EABSException.Create(30094, ErrorGArgumentExpected,
                        [GetOperatorName(op), Token.LineNum, Token.ColumnNum]);
    // ','
    if (Token.TokenType <> tktComma) and not IsReservedWord(Token, rwFrom) then
     begin
      arg1.Free;
      raise EABSException.Create(30095, ErrorGOtherTokenExpected,
                             [',', Token.Text, Token.LineNum, Token.ColumnNum]);
     end;
    // get next token
    GetNextToken;

    // format
    if Token.TokenType <> tktQuotedString then
     begin
      arg1.Free;
      raise EABSException.Create(30096, ErrorGConstDateFormatExpected,
                             [Token.Text, Token.LineNum, Token.ColumnNum]);
     end;

    Result := TABSExprNodeDateFunction.Create(doTOSTRING, arg1, Token.Text);

    // get next token
    GetNextToken;

    // ')'
    if Token.TokenType <> tktRightParenthesis then
      raise EABSException.Create(30097, ErrorGOtherTokenExpected,
                             [')', Token.Text, Token.LineNum, Token.ColumnNum]);
    // get next token
    GetNextToken;
   end
  else
  // Functions with 3 agruments
  if op = doSUBSTRING then
   begin
    // '('
    if Token.TokenType <> tktLeftParenthesis then
      raise EABSException.Create(30098, ErrorGOtherTokenExpected,
                             ['(', Token.Text, Token.LineNum, Token.ColumnNum]);

    // get next token
    GetNextToken;

    //SUBSTRING(str from|, startindex [for|, length])
    // string
    arg1 := ParseCharacterValueExpression;
    if arg1 = nil then
     raise EABSException.Create(30099, ErrorGArgumentExpected,
                       [GetOperatorName(op), Token.LineNum, Token.ColumnNum]);
    // ','
    if (Token.TokenType <> tktComma) and not IsReservedWord(Token, rwFrom) then
     begin
      arg1.Free;
      raise EABSException.Create(30100, ErrorGOtherTokenExpected,
                            [','' or ''from', Token.LineNum, Token.ColumnNum]);
     end;
    // get next token
    GetNextToken;

    // StartIndex
    arg2 := ParseNumericValueExpression;
    if arg2 = nil then
     begin
      arg1.Free;
     raise EABSException.Create(30101, ErrorGArgumentExpected,
                       [GetOperatorName(op), Token.LineNum, Token.ColumnNum]);
     end;

    // ','
    if (Token.TokenType <> tktComma) and not IsReservedWord(Token, rwFor) and
       (Token.TokenType <> tktRightParenthesis) then
     begin
      arg1.Free;
      arg2.Free;
      raise EABSException.Create(30103, ErrorGOtherTokenExpected,
                     [')'' or '','' or ''FOR', Token.LineNum, Token.ColumnNum]);
     end;
    // lenghth
    if Token.TokenType = tktRightParenthesis then
     arg3 := nil
    else
     begin
      // get next token
      GetNextToken;
      arg3 := ParseNumericValueExpression;
     end;
     
    // ')'
    if Token.TokenType <> tktRightParenthesis then
      raise EABSException.Create(30104, ErrorGOtherTokenExpected,
                             [')', Token.Text, Token.LineNum, Token.ColumnNum]);
    // get next token
    GetNextToken;
    if arg3 = nil then
      Result := TABSExprNodeStringFunction.Create(op, arg1,arg2)
    else
    Result := TABSExprNodeStringFunction.Create(op, arg1,arg2,arg3);
   end;
end;//ParseCharacterValueFunction


//------------------------------------------------------------------------------
// parses <datetime value expression>
//------------------------------------------------------------------------------
function TABSExpression.ParseDatetimeValueExpression: TABSExprNode;
var
 SavedTokenNo: integer;        // saved/restored information
begin
{
 <datetime value expression> ::=
   <datetime CURRENT_TIMESTAMP function>
   | <datetime CURRENT_TIME function>
   | <datetime CURRENT_DATE function>
   | <datetime to_date function>
   | :ParameterName
}

 SaveState(SavedTokenNo);
 Result := ParseSysdateFunction;
 if (Result = nil) then
  begin
    // restore original state
    RestoreState(SavedTokenNo);
    Result := ParseCurrentTimeFunction;
  end;
 if (Result = nil) then
  begin
    // restore original state
    RestoreState(SavedTokenNo);
    Result := ParseCurrentDateFunction;
  end;
 if (Result = nil) then
  begin
    // restore original state
    RestoreState(SavedTokenNo);
    Result := ParseToDateFunction;
  end;
 if (Result = nil) then
  begin
    // restore original state
    RestoreState(SavedTokenNo);
    if (Token.TokenType = tktParameter) then
      begin
        if Token.ParamValue.IsDateTimeDataType then
          begin
            Result := TABSExprNodeConst.Create;
            Result.Value.Assign(Token.ParamValue);
            // get next token
            GetNextToken;
          end;
      end;
  end;
end;//ParseDatetimeValueExpression



//------------------------------------------------------------------------------
// parse <datetime sysdate function>
//------------------------------------------------------------------------------
function TABSExpression.ParseSysdateFunction: TABSExprNode;
begin
 if IsReservedWord(Token, rwSYSDATE) or
    IsReservedWord(Token, rwNOW) or
    IsReservedWord(Token, rwCURRENT_TIMESTAMP) then
  begin
   // get next token
   GetNextToken;
   Result := TABSExprNodeDateFunction.Create(doSYSDATE);
  end
 else
  Result := nil;
end;//ParseSysdateFunction


//------------------------------------------------------------------------------
// parse <datetime CURRENT_TIME function>
//------------------------------------------------------------------------------
function TABSExpression.ParseCurrentTimeFunction: TABSExprNode;
begin
 if IsReservedWord(Token, rwCURRENT_TIME) then
  begin
   // get next token
   GetNextToken;
   Result := TABSExprNodeDateFunction.Create(doCURRENT_TIME);
  end
 else
  Result := nil;
end;//ParseCurrentTimeFunction


//------------------------------------------------------------------------------
// parse <datetime CURRENT_DATE function>
//------------------------------------------------------------------------------
function TABSExpression.ParseCurrentDateFunction: TABSExprNode;
begin
 if IsReservedWord(Token, rwCURRENT_DATE) then
  begin
   // get next token
   GetNextToken;
   Result := TABSExprNodeDateFunction.Create(doCURRENT_DATE);
  end
 else
  Result := nil;
end;//ParseCurrentDateFunction


//------------------------------------------------------------------------------
// parse <datetime TODATE function>
//------------------------------------------------------------------------------
function TABSExpression.ParseToDateFunction: TABSExprNode;
begin
{
   ToDate(string, format)
}
 if IsReservedWord(Token, rwTODATE) then
  begin
   // get next token
   GetNextToken;
   // '('
   if Token.TokenType <> tktLeftParenthesis then
      raise EABSException.Create(30105, ErrorGOtherTokenExpected,
                             ['(', Token.Text, Token.LineNum, Token.ColumnNum]);
   // get next token
   GetNextToken;

   // string
   Result := ParseCharacterValueExpression;
   if Result = nil then
    raise EABSException.Create(30107, ErrorGArgumentExpected,
                                    ['ToDate', Token.LineNum, Token.ColumnNum]);
   // ','
   if Token.TokenType <> tktComma then
    begin
      Result.Free;
      raise EABSException.Create(30108, ErrorGOtherTokenExpected,
                             [',', Token.Text, Token.LineNum, Token.ColumnNum]);
    end;
   // get next token
   GetNextToken;

   // format
   if Token.TokenType <> tktQuotedString then
    begin
     Result.Free;
     raise EABSException.Create(30109, ErrorGConstDateFormatExpected,
                                  [Token.Text, Token.LineNum, Token.ColumnNum]);
    end;

   Result := TABSExprNodeDateFunction.Create(doTODATE, Result, Token.Text);

   // get next token
   GetNextToken;

   // ')'
   if Token.TokenType <> tktRightParenthesis then
    begin
     Result.Free;
      raise EABSException.Create(30106, ErrorGOtherTokenExpected,
                             [')', Token.Text, Token.LineNum, Token.ColumnNum]);
    end;
   // get next token
   GetNextToken;
  end
 else
  Result := nil;
end;//ParseToDateFunction


//------------------------------------------------------------------------------
// parse <interval value expression>
//------------------------------------------------------------------------------
function TABSExpression.ParseIntervalValueExpression: TABSExprNode;
var
 SavedTokenNo: integer;        // saved/restored information
 DatetimeTerm: TABSExprNode;
begin
{
    <datetime value expression> <minus sign> <datetime term>
  | <string value expression> <minus sign> <datetime value expression>
  | <left paren> <interval value expression> <right paren>
}
  SaveState(SavedTokenNo);
  // <datetime value expression> <minus sign> <datetime term>
  Result := ParseDatetimeValueExpression;
  if (Result <> nil) then
    begin
      if Token.Text = '-' then
        begin
          GetNextToken;
          DatetimeTerm := ParseValueExpression;
          if (DatetimeTerm <> nil) then
            begin
              Result := TABSExprNodeArithmetic.Create(doADD, Result);
              DatetimeTerm := TABSExprNodeArithmetic.Create(doSUB, DatetimeTerm);
              Result.Children.Add(DatetimeTerm);
            end
          else
            begin
              Result.Free;
              Result := nil;
            end;
        end
      else if Token.Text = '+' then
        begin
          GetNextToken;
          DatetimeTerm := ParseValueExpression;
          if (DatetimeTerm <> nil) then
            begin
              Result := TABSExprNodeArithmetic.Create(doADD, Result);
              DatetimeTerm := TABSExprNodeArithmetic.Create(doADD, DatetimeTerm);
              Result.Children.Add(DatetimeTerm);
            end
          else
            begin
              Result.Free;
              Result := nil;
            end;
        end
        else 
        begin
          Result.Free;
          Result := nil;
        end;
    end
  else
    begin
      RestoreState(SavedTokenNo);

      Result := ParseStringValueExpression;
      if (Result <> nil) then
        begin
          if Token.Text = '-' then
            begin
              GetNextToken;
              DatetimeTerm := ParseDatetimeValueExpression;
              if (DatetimeTerm <> nil) then
                begin
                  Result := TABSExprNodeArithmetic.Create(doADD, Result);
                  DatetimeTerm := TABSExprNodeArithmetic.Create(doSUB, DatetimeTerm);
                  Result.Children.Add(DatetimeTerm);
                end
              else
                begin
                  Result.Free;
                  Result := nil;
                end;
            end
          else
            begin
              Result.Free;
              Result := nil;
            end;
        end
      else
        begin
          // <left paren> <interval value expression> <right paren>
          GetNextToken;
          // '('
          if Token.TokenType = tktLeftParenthesis then
            begin
              // get next token
              GetNextToken;
              Result := ParseIntervalValueExpression;
              if(Result <> nil) then
                begin
                  GetNextToken;
                  if Token.TokenType <> tktRightParenthesis then
                    raise EABSException.Create(30501, ErrorGOtherTokenExpected,
                                             [')', Token.Text, Token.LineNum, Token.ColumnNum]);
                end;
            end;
        end;
    end;
end;// ParseIntervalValueExpression


//------------------------------------------------------------------------------
// parse <case expression>
//------------------------------------------------------------------------------
function TABSExpression.ParseCaseExpression: TABSExprNode;
var
 SavedTokenNo: integer;        // saved/restored information
begin
 SaveState(SavedTokenNo);
 Result := ParseCaseAbbreviation;
 if (Result = nil) then
  begin
    // restore original state
    RestoreState(SavedTokenNo);
    Result := ParseCaseSpecification;
  end;
end;//ParseCaseExpression


//------------------------------------------------------------------------------
// parse <case abbreviation>
//------------------------------------------------------------------------------
function TABSExpression.ParseCaseAbbreviation: TABSExprNode;
begin
  Result := ParseCoalesce;
end;


//------------------------------------------------------------------------------
// parse COALESCE <left paren> <value expression>
//            { <comma> <value expression> }... <right paren>
//------------------------------------------------------------------------------
function TABSExpression.ParseCoalesce: TABSExprNode;
var
  CoalesceNodes: TABSExprNodeArray;
  NodeCount: Integer;
begin
  Result := nil;
  if IsReservedWord(Token, rwCOALESCE) then
    begin
    GetNextToken;
    // '('
    if Token.TokenType <> tktLeftParenthesis then
      raise EABSException.Create(20275, ErrorGOtherTokenExpected,
                             ['(', Token.Text, Token.LineNum, Token.ColumnNum]);
    GetNextToken;
    NodeCount := 0;
    while (Token.TokenType <> tktRightParenthesis) do
      begin
        Inc(NodeCount);
        SetLength(CoalesceNodes, NodeCount);
        CoalesceNodes[NodeCount-1] := ParseValueExpression;
        if (Token.TokenType = tktComma) then
          GetNextToken;
      end;
    GetNextToken;
    if (NodeCount = 0) then
      raise EABSException.Create(20276, ErrorACoalesceArgumentsMissed,
                             [Token.LineNum, Token.ColumnNum]);
    Result := TABSExprNodeCase.CreateCoalesce(CoalesceNodes);
   end;
end;// ParseCoalesce


//------------------------------------------------------------------------------
// parse <case specification>
//------------------------------------------------------------------------------
function TABSExpression.ParseCaseSpecification: TABSExprNode;
begin
  Result := nil;
  if IsReservedWord(Token, rwCASE) then
   begin
    GetNextToken;
    if IsReservedWord(Token, rwWHEN) then
      Result := ParseSearchedCase
    else
      Result := ParseSimpleCase
   end;
end;//ParseCaseSpecification


//------------------------------------------------------------------------------
// parse <simple case>
//------------------------------------------------------------------------------
function TABSExpression.ParseSimpleCase: TABSExprNode;
var
  InputExpression:  TABSExprNode;
  WhenExpression:   TABSExprNode;
  ResultExpression: TABSExprNode;
  ElseResultExpression: TABSExprNode;
begin
{
<simple case> ::=
    CASE <case operand>
      <simple when clause>...
      [ <else clause> ]
    END

<case operand> ::= <value expression>

<simple when clause> ::= WHEN <when operand> THEN <result>

<when operand> ::= <value expression>

<result> ::= <result expression> | NULL

<result expression> ::= <value expression>

<else clause> ::= ELSE <result>
}

  // InputExpression
  InputExpression := ParseValueExpression;
  if InputExpression = nil then
    raise EABSException.Create(30467, ErrorGCaseNoInputExpression,
                               [Token.Text, Token.LineNum, Token.ColumnNum]);
  //GetNextToken;

  Result := TABSExprNodeCase.CreateSimpleCase(InputExpression);
  Repeat
    // WHEN
    if not IsReservedWord(Token, rwWHEN) then
      raise EABSException.Create(30468, ErrorGOtherTokenExpected,
                             ['WHEN', Token.Text, Token.LineNum, Token.ColumnNum]);
    GetNextToken;

    // WhenExpression
    WhenExpression := ParseValueExpression;
    if (WhenExpression = nil) then
     begin
      FreeAndNil(Result);
      raise EABSException.Create(30469, ErrorGCaseNoWhenExpression,
                             [Token.Text, Token.LineNum, Token.ColumnNum]);
     end;
    TABSExprNodeCase(Result).AddWhenExpression(WhenExpression);
    //GetNextToken;

    // THEN
    if not IsReservedWord(Token, rwTHEN) then
     begin
      FreeAndNil(Result);
      raise EABSException.Create(30470, ErrorGOtherTokenExpected,
                             ['THEN', Token.Text, Token.LineNum, Token.ColumnNum]);
     end;
    GetNextToken;

    // ResultExpression
    ResultExpression := ParseValueExpression;
    if (ResultExpression = nil) then
     begin
      FreeAndNil(Result);
      raise EABSException.Create(30469, ErrorGCaseNoWhenExpression,
                             [Token.Text, Token.LineNum, Token.ColumnNum]);
     end;
    TABSExprNodeCase(Result).AddResultExpression(ResultExpression);
    //GetNextToken;

    // Add to Case
  Until not IsReservedWord(Token, rwWHEN);


  // [ELSE]
  if IsReservedWord(Token, rwELSE) then
   begin
    GetNextToken;
    ElseResultExpression := ParseValueExpression;
    if (ElseResultExpression = nil) then
     begin
      FreeAndNil(Result);
      raise EABSException.Create(30471, ErrorGCaseNoElseExpression,
                             [Token.Text, Token.LineNum, Token.ColumnNum]);
     end;
    TABSExprNodeCase(Result).SetElseResultExpression(ElseResultExpression);
   end;

  // END
  if not IsReservedWord(Token, rwEND) then
    begin
      FreeAndNil(Result);
      raise EABSException.Create(30472, ErrorGOtherTokenExpected,
                             ['END', Token.Text, Token.LineNum, Token.ColumnNum]);
    end;

  GetNextToken;

end;//ParseSimpleCase


//------------------------------------------------------------------------------
// parse <searched case>
//------------------------------------------------------------------------------
function TABSExpression.ParseSearchedCase: TABSExprNode;
var
  SearchCondition:   TABSExprNode;
  ResultExpression: TABSExprNode;
  ElseResultExpression: TABSExprNode;
begin
{
<searched case> ::=
    CASE
      <searched when clause>...
      [ <else clause> ]
    END

<searched when clause> ::= WHEN <search condition> THEN <result>

<result> ::= <result expression> | NULL

<result expression> ::= <value expression>

<else clause> ::= ELSE <result>

}

  Result := TABSExprNodeCase.CreateSearchedCase;
  Repeat
    // WHEN
    if not IsReservedWord(Token, rwWHEN) then
      raise EABSException.Create(30473, ErrorGOtherTokenExpected,
                             ['WHEN', Token.Text, Token.LineNum, Token.ColumnNum]);
    GetNextToken;

    // SearchCondition
    SearchCondition := ParseSearchCondition;
    if (SearchCondition = nil) then
     begin
      FreeAndNil(Result);
      raise EABSException.Create(30474, ErrorGCaseNoSearchCondition,
                             [Token.Text, Token.LineNum, Token.ColumnNum]);
     end;
    TABSExprNodeCase(Result).AddWhenExpression(SearchCondition);
    GetCurrentToken;

    // THEN
    if not IsReservedWord(Token, rwTHEN) then
     begin
      FreeAndNil(Result);
      raise EABSException.Create(30475, ErrorGOtherTokenExpected,
                             ['THEN', Token.Text, Token.LineNum, Token.ColumnNum]);
     end;
    GetNextToken;

    // ResultExpression
    ResultExpression := ParseValueExpression;
    if (ResultExpression = nil) then
     begin
      FreeAndNil(Result);
      raise EABSException.Create(30476, ErrorGCaseNoWhenExpression,
                             [Token.Text, Token.LineNum, Token.ColumnNum]);
     end;
    TABSExprNodeCase(Result).AddResultExpression(ResultExpression);
    GetCurrentToken;

    // Add to Case
  Until not IsReservedWord(Token, rwWHEN);


  // [ELSE]
  if IsReservedWord(Token, rwELSE) then
   begin
    GetNextToken;
    ElseResultExpression := ParseValueExpression;
    if (ElseResultExpression = nil) then
     begin
      FreeAndNil(Result);
      raise EABSException.Create(30477, ErrorGCaseNoElseExpression,
                             [Token.Text, Token.LineNum, Token.ColumnNum]);
     end;
    TABSExprNodeCase(Result).SetElseResultExpression(ElseResultExpression);
    GetCurrentToken;
   end;

  // END
  if not IsReservedWord(Token, rwEND) then
    begin
      FreeAndNil(Result);
      raise EABSException.Create(30478, ErrorGOtherTokenExpected,
                             ['END', Token.Text, Token.LineNum, Token.ColumnNum]);
    end;

  GetNextToken;

end;//ParseSearchedCase


//------------------------------------------------------------------------------
// ParseBlobExpression
//------------------------------------------------------------------------------
function TABSExpression.ParseBlobExpression: TABSExprNode;
begin
  Result := ParseMimeToBinFunction;
end;//ParseBlobExpression


//------------------------------------------------------------------------------
// parse MimeToBin Function
//------------------------------------------------------------------------------
function TABSExpression.ParseMimeToBinFunction: TABSExprNode;
var
  StrConst: TABSExprNode;
begin
  Result := nil;
  if IsReservedWord(Token, rwMIMETOBIN) then
   begin
     GetNextToken;
     // '('
     if (Token.TokenType <> tktLeftParenthesis) then
       raise EABSException.Create(30479, ErrorGOtherTokenExpected,
                             ['(', Token.Text, Token.LineNum, Token.ColumnNum]);
     GetNextToken;

     // 'Mime String'
     if (Token.TokenType <> tktQuotedString) then
         raise EABSException.Create(30480, ErrorGStringArgumentExpected,
                             ['MimeToBin()', Token.Text, Token.LineNum, Token.ColumnNum]);

     StrConst := TABSExprNodeConst.Create;
     StrConst.Value.AsAnsiString := AnsiString(Token.Text);
     Result := TABSExprNodeBlob.Create(doMIMETOBIN, StrConst);
     GetNextToken;

     // ')'
     if (Token.TokenType <> tktRightParenthesis) then
       begin
         FreeAndNil(Result);
         raise EABSException.Create(30481, ErrorGOtherTokenExpected,
                             [')', Token.Text, Token.LineNum, Token.ColumnNum]);
       end;
     GetNextToken;
   end;
end;//ParseMimeToBinFunction


////////////////////////////////////////////////////////////////////////////////
//
// TABSExprNode
//
////////////////////////////////////////////////////////////////////////////////


//------------------------------------------------------------------------------
// Constructor
//------------------------------------------------------------------------------
constructor TABSExprNode.Create(CaseInsensitive, PartialKey: Boolean);
begin
  Operator := doUNDEFINED;
  Value := TABSVariant.Create;
  Children := TList.Create;
  FCaseInsensitive := CaseInsensitive;
  FPartialKey := PartialKey;
end;//Create


//------------------------------------------------------------------------------
// Constructor
//------------------------------------------------------------------------------
constructor TABSExprNode.Create(Op: TABSDataOperator; CaseInsensitive, PartialKey: Boolean);
begin
  Create(CaseInsensitive, PartialKey);
  Operator := Op;
end;//Create


//------------------------------------------------------------------------------
// Constructor
//------------------------------------------------------------------------------
constructor TABSExprNode.Create(Op: TABSDataOperator; Node: TABSExprNode; CaseInsensitive, PartialKey: Boolean);
begin
  Create(Op, CaseInsensitive, PartialKey);
  Children.Add(Node);
end;//Create


//------------------------------------------------------------------------------
// Constructor
//------------------------------------------------------------------------------
constructor TABSExprNode.Create(Op: TABSDataOperator; Node1, Node2: TABSExprNode; CaseInsensitive, PartialKey: Boolean);
begin
  Create(Op, Node1, CaseInsensitive, PartialKey);
  Children.Add(Node2);
end;//Create


//------------------------------------------------------------------------------
// Constructor
//------------------------------------------------------------------------------
constructor TABSExprNode.Create(Op: TABSDataOperator; Node1, Node2, Node3: TABSExprNode; CaseInsensitive, PartialKey: Boolean);
begin
  Create(Op, Node1, Node2, CaseInsensitive, PartialKey);
  Children.Add(Node3);
end;//Create


//------------------------------------------------------------------------------
// Destructor
//------------------------------------------------------------------------------
destructor TABSExprNode.Destroy;
var i: Integer;
begin
  if (Children <> nil) then
   for i:=0 to Children.Count-1 do
    TABSExprNode(Children[i]).Free;
  if (Children <> nil) then
    Children.Free;
  Children := nil;
  if (Value <> nil) then
    Value.Free;
  Value := nil;
  inherited;
end;//Destroy


//------------------------------------------------------------------------------
// converts some WideStrings to Strings
//------------------------------------------------------------------------------
procedure TABSExprNode.PatchWideStrings;
var i: Integer;
begin
  for i:=0 to Children.Count-1 do
    TABSExprNode(Children[i]).PatchWideStrings;
end;//PatchWideStrings


//------------------------------------------------------------------------------
// calc subquery field values again
//------------------------------------------------------------------------------
procedure TABSExprNode.ResetSubqueryFieldValues;
begin
end;// ResetSubqueryFieldValues


//------------------------------------------------------------------------------
// Return subquery field value
//------------------------------------------------------------------------------
function TABSExprNode.GetSubqueryFieldValue(FieldNo: Integer): TABSVariant;
begin
  Result := nil;
end;// GetSubqueryFieldValue


//------------------------------------------------------------------------------
// is expression contains aggregated function
//------------------------------------------------------------------------------
function TABSExprNode.IsAggregated: Boolean;
var i: Integer;
begin
  Result := False;
  for i:=0 to Children.Count-1 do
   begin
    if Children[i] = nil then continue;
    Result := TABSExprNode(Children[i]).IsAggregated;
    if Result then Break;
   end;
end;//isAggregated


//------------------------------------------------------------------------------
// Is count(*)
//------------------------------------------------------------------------------
function TABSExprNode.IsAggregatedCountAll: Boolean;
begin
  Result := False;
end;// IsAggregatedCountAll


//------------------------------------------------------------------------------
// Init for group function
//------------------------------------------------------------------------------
procedure TABSExprNode.Init;
var i: integer;
begin
  for i:=0 to Children.Count-1 do
   TABSExprNode(Children[i]).Init;
end;//Init


//------------------------------------------------------------------------------
// Accumulate for group functions
//------------------------------------------------------------------------------
procedure TABSExprNode.Accumulate;
var i: integer;
begin
  for i:=0 to Children.Count-1 do
   TABSExprNode(Children[i]).Accumulate;
end;//Accumulate


//------------------------------------------------------------------------------
// SetCountAll
//------------------------------------------------------------------------------
procedure TABSExprNode.SetCountAll(RecordCount: Integer);
begin

end;// SetCountAll


//------------------------------------------------------------------------------
// assign AO
//------------------------------------------------------------------------------
procedure TABSExprNode.AssignAO(AO, ParentQueryAO: TABSAO; ParentCursor: TABSCursor);
var
  i: integer;
begin
  for i:=0 to Children.Count-1 do
    TABSExprNode(Children[i]).AssignAO(AO, ParentQueryAO, ParentCursor);
end;//AssignAO


//------------------------------------------------------------------------------
// Assign Cursor
//------------------------------------------------------------------------------
procedure TABSExprNode.AssignCursor(Cursor: TABSCursor; ParentQueryAO: TABSAO; ParentCursor: TABSCursor);
var
  i: integer;
begin
  for i:=0 to Children.Count-1 do
    TABSExprNode(Children[i]).AssignCursor(Cursor, ParentQueryAO, ParentCursor);
end;//AssignCursor


//------------------------------------------------------------------------------
// process assign New Cursor Buffer
//------------------------------------------------------------------------------
procedure TABSExprNode.AssignCursorBuffer(Buffer: TABSRecordBuffer; ApplyToParentQueryFields: Boolean);
var i: integer;
begin
  for i:=0 to Children.Count-1 do
    TABSExprNode(Children[i]).AssignCursorBuffer(Buffer, ApplyToParentQueryFields);
end;//AssignCursorBuffer


//------------------------------------------------------------------------------
// get Data Size
//------------------------------------------------------------------------------
function TABSExprNode.GetDataSize: Integer;
var
  i: integer;
  ftype: TABSBaseFieldType;
  child: TABSExprNode;
begin
  Result := 0;
  ftype := AdvancedFieldTypeToBaseFieldType(getDataType);
  //if ftype in [ftString, ftWideString, ftBytes] then
  if IsStringFieldType(ftype) then
   begin
    if Operator in [doADD, doCONCAT, doCAST] then
     for i:=0 to Children.Count-1 do
      begin
       child := TABSExprNode(Children[i]);
       Result := Result + child.getDataSize;
       {case child.getDataType of
         ftString,
         ftBytes,
         ftWideString:
           Result := Result + child.getDataSize;
         ftAutoInc,
         ftInteger:
           Result := Result + 11;
         ftSmallInt:
           Result := Result + 6;
         ftWord:
           Result := Result + 5;
         ftLargeInt:
           Result := Result + 20;
         ftFloat,
         ftDate,
         ftTime,
         ftDateTime:
           Result := Result + 26;
         ftBoolean:
           Result := Result + 5;
         ftCurrency:
           Result := Result + 21;
       end;
       }
      end;
   end;
//  if ftype = ftWideString then
//    Result := Result * 2;
end;//GetDataSize


//------------------------------------------------------------------------------
// return Type of Data
//------------------------------------------------------------------------------
function TABSExprNode.GetDataType: TABSAdvancedFieldType;
var
  i: integer;
begin
  // default ftUnknown - Error flag
  Result := aftUnknown;
  if Children.Count <> 0 then
   begin
    // Take first type
    Result := TABSExprNode(Children[0]).getDataType;
    // and compare it with other
    for i:=1 to Children.Count-1 do
      begin
        Result := getCommonDataType(Result, TABSExprNode(Children[i]).getDataType);
      end;
   end;
  if (Result = aftUnknown) then
    raise EABSException.Create(30283, ErrorGCannotDetermineExpressionType);
end;//GetDataType


//------------------------------------------------------------------------------
// return Data Precision
//------------------------------------------------------------------------------
function TABSExprNode.GetPrecision: Integer;
begin
  Result := 0;
end;//GetPrecision


//------------------------------------------------------------------------------
// can be used by the AO?
//------------------------------------------------------------------------------
function TABSExprNode.CanBeAssigned(AO, ParentQueryAO: TABSAO;
                                    ParentCursor: TABSCursor;
                                    ForHavingClause: Boolean = False): Boolean;
var
  i: Integer;
begin
  Result := True;
  // get childs cans
  for i:=0 to Children.Count-1 do
    begin
      Result := Result and TABSExprNode(Children[i]).CanBeAssigned(AO, ParentQueryAO, ParentCursor, ForHavingClause);
      if not Result then break;
    end;
end;//CanBeAssigned


//------------------------------------------------------------------------------
// CanUseNodeAsField
//------------------------------------------------------------------------------
function TABSExprNode.CanUseNodeAsField(Node: TABSExprNode): Boolean;
begin
  Result := (Node is TABSExprNodeField);
  if (Result) then
    Result := not TABSExprNodeField(Node).FGetFromParent
  else
    begin
      if (Node is TABSExprNodeStringFunction) then
        Result := (TABSExprNodeStringFunction(Node).Operator in [doUpper, doLower]) and
                  (TObject(TABSExprNodeStringFunction(Node).Children[0]) is TABSExprNodeField);
      if (Result) then
        Result := not TABSExprNodeField(TABSExprNodeStringFunction(Node).Children[0]).FGetFromParent
    end;
end;// CanUseNodeAsField


//------------------------------------------------------------------------------
// node is const or correlated field from parent query?
//------------------------------------------------------------------------------
function TABSExprNode.CanUseNodeAsConst(Node: TABSExprNode): Boolean;
begin
  Result := (Node is TABSExprNodeConst);
  if (not Result) then
    begin
      Result := (TObject(Node) is TABSExprNodeField);
      if (Result) then
        Result := TABSExprNodeField(Node).FGetFromParent;
    end;
end;// CanUseNodeAsConst


//------------------------------------------------------------------------------
// can use index for comparison?
//------------------------------------------------------------------------------
function TABSExprNode.CanUseIndex(IndexColumnNo: Integer;
         ConditionSearchOperator: TABSSearchCondition; IndexDef: TABSIndexDef;
         SessionID: TABSSessionID = INVALID_SESSION_ID): Boolean;
begin
  Result := False;
end;// CanUseIndex


//------------------------------------------------------------------------------
// fill scan condition props
//------------------------------------------------------------------------------
procedure TABSExprNode.FillScanCondition(Value:         TABSVariant;
                                         Cursor:        TABSCursor;
                                         IndexColumnNo: Integer;
                                         FieldNo:       Integer;
                                         IndexDef:      TABSIndexDef;
                                         Expression:    TABSExpression;
                                         ScanCondition: TABSScanSearchCondition);
begin
  Cursor.SetFieldValue(Value,FieldNo,True,ScanCondition.KeyRecordBuffer);
  ScanCondition.KeyFieldCount := ScanCondition.KeyFieldCount + 1;
  ScanCondition.ParentExpressions.Add(Expression);
  ScanCondition.ParentExpressionNodes.Add(Self);
end;// FillScanCondition


//------------------------------------------------------------------------------
// add index scan condition
//------------------------------------------------------------------------------
procedure TABSExprNode.AddAsIndexScanCondition(
               ScanConditions: TABSScanSearchConditionArray;
               IndexDef:       TABSIndexDef;
               Expression:  TABSExpression);
begin

end;// AddAsIndexScanCondition


//------------------------------------------------------------------------------
// add checks to multi-field index scan condition
//------------------------------------------------------------------------------
procedure TABSExprNode.ExtendIndexScanCondition(
               ScanCondition: TABSScanSearchCondition;
               IndexDef:      TABSIndexDef;
               Expression:    TABSExpression);
begin

end;// ExtendIndexScanCondition


//------------------------------------------------------------------------------
// is node a join condition?
//------------------------------------------------------------------------------
function TABSExprNode.IsJoinCondition(AO1, AO2: TABSAO): Boolean;
begin
  Result := False;
end;// IsJoinConditions


//------------------------------------------------------------------------------
// replace pseudonyms to original names (f1 -> table1.field1)
//------------------------------------------------------------------------------
procedure TABSExprNode.ReplacePseudonyms(SelectList: array of TABSSelectListItem);
var
  i: integer;
begin
  for i:=0 to Children.Count-1 do
    TABSExprNode(Children[i]).ReplacePseudonyms(SelectList);
end;//ReplacePseudonyms


//------------------------------------------------------------------------------
// check if agregate expr, const or only grouped fields are used
//------------------------------------------------------------------------------
function TABSExprNode.CanBeUsedInGroupBy(GroupByFields: string): Boolean;
var
  i: Integer;
begin
  Result := True;
  // get childs cans
  for i:=0 to Children.Count-1 do
    begin
      Result := Result and TABSExprNode(Children[i]).CanBeUsedInGroupBy(GroupByFields);
      if not Result then break;
    end;
end;// CanBeUsedInGroupBy



////////////////////////////////////////////////////////////////////////////////
//
// TABSExprNodeField
//
////////////////////////////////////////////////////////////////////////////////


//------------------------------------------------------------------------------
// Constructor
//------------------------------------------------------------------------------
constructor TABSExprNodeField.Create(Cursor: TABSCursor; FieldName, TableName: string);
begin
  inherited Create;

  LCursor := Cursor;
  FFieldName := FieldName;
  FTableName := TableName;
  FFieldNameInAO := '';

  FFieldNo := -1;
  FFieldOffsetInBuffer := 0;
  FFieldType := aftUnknown;
  FBaseFieldType := bftUnknown;
  FFieldSize := 0;
  FFieldPrecision := 0;
  LRecordBuffer := nil;
  FGetFromParent := False;
  FScanOnlyVisibleFieldsInAO := False;

  if LCursor <> nil then
    AssignCursor(Cursor, nil, nil);
end;//Create


//------------------------------------------------------------------------------
// Return Data Value
//------------------------------------------------------------------------------
function TABSExprNodeField.GetDataValue: TABSVariant;
var
  bs: TABSStream;
  s: AnsiString;
  OldRecordBuffer: TABSRecordBuffer;
begin
{$IFDEF DEBUG_TRACE_EXPRESSIONS}aaWriteToLog('TABSExprNodeField.GetDataValue start');{$ENDIF}
try
  Value.SetNull;
  if (LRecordBuffer <> nil) then
    begin
    // If field not null then set Value variable
    if (not CheckNullFlag(FFieldNo, LRecordBuffer)) then
      begin
        if FBaseFieldType = bftClob then
          begin
            OldRecordBuffer := LCursor.CurrentRecordBuffer;
            LCursor.CurrentRecordBuffer := LRecordBuffer;
            try
              bs := TABSLocalCursor(LCursor).TableData.InternalCreateBlobStream(
                                                LCursor,False,FFieldNo,bomRead);
            finally
              LCursor.CurrentRecordBuffer := OldRecordBuffer;
            end;
            try
              SetLength(s, bs.Size);
              bs.ReadBuffer(TAbsPByte(s)^, bs.Size);
              Value.AsAnsiString := s;
            finally
              TABSLocalCursor(LCursor).TableData.ClearBlobsCacheInRecordBuffer(LRecordBuffer);
              bs.Free;
            end;
          end
        else
        if FBaseFieldType = bftWideClob then
          begin
            OldRecordBuffer := LCursor.CurrentRecordBuffer;
            LCursor.CurrentRecordBuffer := LRecordBuffer;
            try
              bs := TABSLocalCursor(LCursor).TableData.InternalCreateBlobStream(
                                                LCursor,False,FFieldNo,bomRead);
            finally
              LCursor.CurrentRecordBuffer := OldRecordBuffer;
            end;
            try
              SetLength(s, bs.Size);
              bs.ReadBuffer(TAbsPByte(s)^, bs.Size);
              Value.AsWideString := WideString(TAbsPByte(s));
            finally
              TABSLocalCursor(LCursor).TableData.ClearBlobsCacheInRecordBuffer(LRecordBuffer);
              bs.Free;
            end;
          end
        else
          Value.SetData(LRecordBuffer + FFieldOffsetInBuffer, FFieldSize, FBaseFieldType, False)
      end;
    end
  else
    if ((LAO <> nil) and (FFieldNo <> -1) ) then
      LAO.GetFieldValue(Value, FFieldNo, true, true)
    else
      raise EABSException.Create(30196, ErrorGDatasetAndAONotAssigned);
  Result := Value;
finally
{$IFDEF DEBUG_TRACE_EXPRESSIONS}aaWriteToLog('TABSExprNodeField.GetDataValue end');{$ENDIF}
end;
end;//GetDataValue


//------------------------------------------------------------------------------
// TryAssignAO
//------------------------------------------------------------------------------
function TABSExprNodeField.TryAssignAO(AOToAssign: TABSAO): Boolean;
var
  Fields: TABSIntegerArray;
begin
  Result := False;
  if AOToAssign <> nil then
   begin
    Fields := TABSIntegerArray.Create;
    try
      AOToAssign.FieldExists(FFieldName, FTableName, not FScanOnlyVisibleFieldsInAO, Fields, True, FScanOnlyVisibleFieldsInAO);
      if Fields.ItemCount > 0 then
        begin
          // Store Field Parameters
          FFieldNo := Fields.items[0];
          FFieldNameInAO := AOToAssign.GetFieldNameByColumnNo(FFieldNo);
          FFieldOffsetInBuffer := 0;
          FFieldType := AOToAssign.GetFieldType(FFieldNo);
          FBaseFieldType := AdvancedFieldTypeToBaseFieldType(FFieldType);
          FFieldSize := AOToAssign.GetFieldSize(FFieldNo);
          FFieldPrecision := AOToAssign.GetFieldPrecision(FFieldNo);
          Result := True;
        end
    finally
      Fields.Free;
    end;
  end;
end;// TryAssignAO


//------------------------------------------------------------------------------
// process assign AO
//------------------------------------------------------------------------------
procedure TABSExprNodeField.AssignAO(AO, ParentQueryAO: TABSAO; ParentCursor: TABSCursor);
begin
  LCursor := nil;
  LRecordBuffer := nil;
  if (TryAssignAO(AO)) then
    LAO := AO
  else
    if (TryAssignAO(ParentQueryAO)) then
      LAO := ParentQueryAO
    else
     if (FGetFromParent) then
       AssignCursor(ParentCursor, ParentQueryAO, ParentCursor)
     else
      if ((LAO = nil) and (LCursor = nil)) then
        raise EABSException.Create(30197, ErrorGCannotFindField,
                                   [FTableName+'.'+FFieldName]);
end;//AssignAO


//------------------------------------------------------------------------------
// FindFieldInCursor
//------------------------------------------------------------------------------
function TABSExprNodeField.FindFieldInCursor(Cursor: TABSCursor; var FieldNo: Integer): Boolean;
begin
  if (Cursor = nil) then
    Result := False
  else
    begin
      // if field not exists, res = -1
      FieldNo := Cursor.FieldDefs.GetDefNumberByName(FFieldName);
      if (FieldNo = -1) then
        begin
          FieldNo := Cursor.VisibleFieldDefs.GetDefNumberByName(FFieldName);
          if (FieldNo <> -1) then
            FieldNo := Cursor.VisibleFieldDefs[FieldNo].FieldNoReference
          else
            begin
              FieldNo := Cursor.FieldDefs.GetDefNumberByName(FFieldNameInAO);
              if (FieldNo = -1) then
                FieldNo := Cursor.VisibleFieldDefs.GetDefNumberByName(FFieldNameInAO);
            end;
        end;
      Result := (FieldNo <> -1);
    end;
end;// FindFieldInCursor


//------------------------------------------------------------------------------
// process assign Cursor and its buffer
//------------------------------------------------------------------------------
procedure TABSExprNodeField.AssignCursor(Cursor: TABSCursor; ParentQueryAO: TABSAO; ParentCursor: TABSCursor);
var n: Integer;
begin
  if Cursor = nil then
    raise EABSException.Create(30202, ErrorGValueIsNull);

  if (ParentQueryAO <> nil) then
    LAO := nil;
  if (ParentCursor <> nil) then
    LCursor := nil;
  if (FGetFromParent) then
    begin
      if (FindFieldInCursor(ParentCursor, n)) then
        LCursor := ParentCursor
      else
      if (TryAssignAO(ParentQueryAO)) then
        LAO := ParentQueryAO
      else
        exit;
    end
  else
    begin
      LAO := nil;
      LCursor := nil;
      if (FindFieldInCursor(Cursor, n)) then
        LCursor := Cursor
      else
        if (FindFieldInCursor(ParentCursor, n)) then
          LCursor := ParentCursor;
    end;

  if ((LAO = nil) and (LCursor = nil)) then
    raise EABSException.Create(30115, ErrorGFieldWithNameNotFound, [FFieldName]);

  if (LCursor <> nil) then
    begin
      // Store Field Parameters
      FFieldNo := n;
      FFieldOffsetInBuffer := LCursor.FieldDefs[n].MemoryOffset;
      FFieldType := LCursor.FieldDefs[n].AdvancedFieldType;
      FBaseFieldType := AdvancedFieldTypeToBaseFieldType(FFieldType);
      FFieldSize := LCursor.FieldDefs[n].MemoryDataSize;
      AssignCursorBuffer(LCursor.CurrentRecordBuffer, FGetFromParent);
    end;
end;//AssignCursor


//------------------------------------------------------------------------------
// process assign New Cursor Buffer
//------------------------------------------------------------------------------
procedure TABSExprNodeField.AssignCursorBuffer(Buffer: TABSRecordBuffer; ApplyToParentQueryFields: Boolean);
begin
  if (FGetFromParent = ApplyToParentQueryFields) then
    LRecordBuffer := Buffer;
end;//AssignCursorBuffer


//------------------------------------------------------------------------------
// return Type of Data
//------------------------------------------------------------------------------
function TABSExprNodeField.GetDataType: TABSAdvancedFieldType;
begin
  Result := aftUnknown;
  if LCursor <> nil then
    Result := LCursor.FieldDefs[FFieldNo].AdvancedFieldType
  else
    if LAO <> nil then
      Result := LAO.GetFieldType(FFieldNo);

  if (Result = aftUnknown) then
    raise EABSException.Create(30284, ErrorGCannotDetermineExpressionType);
end;//GetDataType


//------------------------------------------------------------------------------
// return Data Size
//------------------------------------------------------------------------------
function TABSExprNodeField.GetDataSize: Integer;
begin
  {Result := 0;
  if LCursor <> nil then
    Result := LCursor.FieldDefs[FFieldNo].MemoryDataSize
  else
    if LAO <> nil then
      Result := LAO.GetFieldSize(FFieldNo);
  }
  Result := FFieldSize;
end;//GetDataSize


//------------------------------------------------------------------------------
// can be used by the AO?
//------------------------------------------------------------------------------
function TABSExprNodeField.CanBeAssigned(AO, ParentQueryAO: TABSAO;
                                         ParentCursor: TABSCursor; ForHavingClause: Boolean = False): Boolean;
var
  Fields: TABSIntegerArray;
  n: integer;
begin
  FGetFromParent := False;
  n := -1;
  if AO <> nil then
    begin
      Fields := TABSIntegerArray.Create;
      try
        AO.FieldExists(FFieldName, FTableName, False, Fields, ForHavingClause);
        if Fields.ItemCount = 1 then
          n := Fields.items[0];
      finally
        Fields.Free;
      end;
    end;
  Result := (n <> -1);
  if (not Result) then
    if ParentQueryAO <> nil then
      begin
        Fields := TABSIntegerArray.Create;
        try
          ParentQueryAO.FieldExists(FFieldName, FTableName, False, Fields, ForHavingClause);
          if Fields.ItemCount = 1 then
            n := Fields.items[0];
          Result := (n <> -1);
          FGetFromParent := Result;
        finally
          Fields.Free;
        end;
      end;
  if (not Result) then
    if ParentCursor <> nil then
      begin
        Result := FindFieldInCursor(ParentCursor, n);
        FGetFromParent := Result;
      end;
  if (Result) then
    FScanOnlyVisibleFieldsInAO := ForHavingClause;
end;


//------------------------------------------------------------------------------
// fills Field Item
//------------------------------------------------------------------------------
procedure TABSExprNodeField.FillItem(var Item: TABSSelectListItem);
begin
  Item.TableName := FTableName;
  Item.FieldName := FFieldName;
end;// FillItem


//------------------------------------------------------------------------------
// replace pseudonyms to original names (f1 -> table1.field1)
//------------------------------------------------------------------------------
procedure TABSExprNodeField.ReplacePseudonyms(SelectList: array of TABSSelectListItem);
var
  i: Integer;
begin
  for i := 0 to Length(SelectList)-1 do
  if (UpperCase(FFieldName) = UpperCase(SelectList[i].Pseudonym)) and
     ((UpperCase(FTableName) = UpperCase(SelectList[i].TableName)) or (FTableName = '')) then
   if (not SelectList[i].IsExpression) then
    begin
      FFieldName := SelectList[i].FieldName;
      FTableName := SelectList[i].TableName;
    end;
end;//ReplacePseudonyms


//------------------------------------------------------------------------------
// check if agregate expr, const or only grouped fields are used
//------------------------------------------------------------------------------
function TABSExprNodeField.CanBeUsedInGroupBy(GroupByFields: string): Boolean;
var
  List: TStringList;
  i: Integer;
begin
  List := TStringList.Create;
  try
    FillFieldNames(List, GroupByFields);
    Result := False;
    for i:=0 to List.Count-1 do
      if (UpperCase(List[i]) = UpperCase(FFieldName)) then
        begin
          Result := True;
          break;
        end;
  finally
    List.Free;
  end;
end;// CanBeUsedInGroupBy


////////////////////////////////////////////////////////////////////////////////
//
// TABSExprNodeSystem
//
////////////////////////////////////////////////////////////////////////////////


//------------------------------------------------------------------------------
// Constructor
//------------------------------------------------------------------------------
constructor TABSExprNodeSystem.Create(Operator: TABSDataOperator; SessionName, DatabaseName, TableName, ColumnName: string; InMemory: Boolean);
var
  FieldDef: TABSAdvFieldDef;
begin
  inherited Create(Operator);
  FTableName := TableName;
  FColumnName := ColumnName;
  FFieldType := aftUnknown;
  FInMemory := InMemory;
  FTable := nil;
  FRowNumValue := 0;
  if (Operator = doLASTAUTOINC) then
    begin
      FTable := TABSTable.Create(nil);
      TABSTable(FTable).SessionName := SessionName;
      TABSTable(FTable).DatabaseName := DatabaseName;
      TABSTable(FTable).TableName := FTableName;
      TABSTable(FTable).InMemory := FInMemory;
      TABSTable(FTable).ReadOnly := True;

      TABSTable(FTable).Open;

      // Field Type
      FieldDef := TABSTable(FTable).AdvFieldDefs.Find(FColumnName);
      if (FieldDef = nil) then
        raise EABSException.Create(30337, ErrorGFieldWithNameNotFound, [FColumnName]);
      FFieldType := FieldDef.DataType;
    end;
end;//Create


//------------------------------------------------------------------------------
// Destructor
//------------------------------------------------------------------------------
destructor TABSExprNodeSystem.Destroy;
begin
  if (FTable <> nil) then
    begin
      TABSTable(FTable).Close;
      FTable.Free;
    end;
  inherited;
end;//Destroy


//------------------------------------------------------------------------------
// LastAutoInc
//------------------------------------------------------------------------------
procedure TABSExprNodeSystem.LastAutoInc;
begin
  Value.SetNull(AdvancedFieldTypeToBaseFieldType(GetDataType));
  Value.AsInt64 := TABSTable(FTable).LastAutoincValue(FColumnName);
end;//LastAutoInc


//------------------------------------------------------------------------------
// RowNum
//------------------------------------------------------------------------------
procedure TABSExprNodeSystem.RowNum;
begin
  Inc(FRowNumValue);
  Value.SetNull;
  Value.AsInteger := FRowNumValue;
end;// RowNum


//------------------------------------------------------------------------------
// Return Data Value
//------------------------------------------------------------------------------
function TABSExprNodeSystem.GetDataValue: TABSVariant;
begin
  case Operator of
    doLASTAUTOINC: LastAutoInc;
    doROWNUM: RowNum;
    else
      raise EABSException.Create(30338, ErrorGUnknownOperator, [GetOperatorName(Operator)]);
  end;

  Result := Value;
end;//GetDataValue


//------------------------------------------------------------------------------
// return Type of Data
//------------------------------------------------------------------------------
function TABSExprNodeSystem.GetDataType: TABSAdvancedFieldType;
begin
  case Operator of
    doLASTAUTOINC: Result := FFieldType;
    doROWNUM: Result := aftInteger;
  end;

end;//GetDataType


//------------------------------------------------------------------------------
// return Size of Data (for strings and arrays)
//------------------------------------------------------------------------------
function TABSExprNodeSystem.GetDataSize: Integer;
begin
  Result := 0;
end;//GetDataSize



////////////////////////////////////////////////////////////////////////////////
//
// TABSExprNodeAggregated
//
////////////////////////////////////////////////////////////////////////////////



//------------------------------------------------------------------------------
// Constructor
//------------------------------------------------------------------------------
constructor TABSExprNodeAggregated.Create(Op: TABSDataOperator);
begin
  inherited Create(Op);
  Count := 0;
  AvgSum := TABSVariant.Create;
end;//Create


//------------------------------------------------------------------------------
// Constructor
//------------------------------------------------------------------------------
constructor TABSExprNodeAggregated.Create(Op: TABSDataOperator;
  distinct: boolean; Node: TABSExprNode);
begin
  inherited Create(op, Node);
  self.distinct := distinct;
  AvgSum := TABSVariant.Create;
end;//Create


//------------------------------------------------------------------------------
// Destructor
//------------------------------------------------------------------------------
destructor TABSExprNodeAggregated.Destroy;
begin
  AvgSum.Free;
  inherited;
end;//Destroy


//------------------------------------------------------------------------------
// Return Data Value
//------------------------------------------------------------------------------
function TABSExprNodeAggregated.GetDataValue: TABSVariant;
begin
  case Operator of
    doCOUNT,
    doCOUNTALL:
      Value.AsInteger := Count;
    doAVG:
      begin
        //   = AvgSum / Count
        Value.Assign(AvgSum);
        AvgSum.AsDouble := Count;
        Value.Division(AvgSum);
      end;
  end;
  Result := Value;
end;//GetDataValue


//------------------------------------------------------------------------------
// return Type of Data
//------------------------------------------------------------------------------
function TABSExprNodeAggregated.GetDataType: TABSAdvancedFieldType;
begin
  Result := aftUnknown;
  case Operator of
    doCOUNTALL,
    doCOUNT:
      Result := aftInteger;
    doMIN,
    doMAX:
      if Children.Count <> 0 then
             Result := TABSExprNode(Children[0]).getDataType;
    doSUM:
      if Children.Count <> 0 then
       begin
        Result := TABSExprNode(Children[0]).getDataType;
        if not (Result in [aftSingle, aftDouble, aftExtended,
                            aftCurrency] ) then
          Result := aftLargeint;
       end;
    doAVG:
      Result := aftDouble;
  end;
end;//GetDataType


//------------------------------------------------------------------------------
// return Size of Data (for strings and arrays)
//------------------------------------------------------------------------------
function TABSExprNodeAggregated.GetDataSize: Integer;
begin
  case Operator of
    doMIN,
    doMAX:
      if Children.Count <> 0 then
        Result := TABSExprNode(Children[0]).getDataSize
      else
        Result := inherited GetDataSize;
  else
    Result := inherited GetDataSize;
  end;
end;// GetDataSize


//------------------------------------------------------------------------------
// is expression contains aggregated function
//------------------------------------------------------------------------------
function TABSExprNodeAggregated.isAggregated: Boolean;
begin
  Result := True;
end;//isAggregated


//------------------------------------------------------------------------------
// Is count(*)
//------------------------------------------------------------------------------
function TABSExprNodeAggregated.IsAggregatedCountAll: Boolean;
begin
  Result := (Operator = doCOUNTALL);
end;// IsAggregatedCountAll


//------------------------------------------------------------------------------
// CanBeAssigned
//------------------------------------------------------------------------------
function TABSExprNodeAggregated.CanBeAssigned(AO, ParentQueryAO: TABSAO;
                                              ParentCursor: TABSCursor; ForHavingClause: Boolean = False): Boolean;
begin
  Result := AO is TABSAOGroupBy;
  if (Result) then
    Result := inherited CanBeAssigned(AO, ParentQueryAO, ParentCursor, False);
end;// CanBeAssigned


//------------------------------------------------------------------------------
// Init for aggregated functions
//------------------------------------------------------------------------------
procedure TABSExprNodeAggregated.Init;
begin
  count := 0;
  Value.SetNull;
  if Operator = doAVG then
    AvgSum.SetNull;
{  if Operator = doSUM then
   Data.DataType := GetDataType;}
end;//Init


//------------------------------------------------------------------------------
// Accumulate for group functions
//------------------------------------------------------------------------------
procedure TABSExprNodeAggregated.Accumulate;
var
  TmpVal: TABSVariant;
begin
  TmpVal := TABSVariant.Create;
  try
    if Children.Count <> 0 then
      TmpVal.Assign(TABSExprNode(Children[0]).GetDataValue, False);
      
    case Operator of
      doMIN:
        begin
         if (Value.IsNull) then
           Value.Assign(TmpVal)
         else
           if (not TmpVal.IsNull) then
             if (TmpVal.Compare(Value) = cmprLower) then
               Value.Assign(TmpVal);
        end;
      doMAX:
        begin
         if (Value.IsNull) then
           Value.Assign(TmpVal)
         else
           if (not TmpVal.IsNull) then
             if (TmpVal.Compare(Value) = cmprGreater) then
               Value.Assign(TmpVal);
        end;
      doCOUNTALL:
        begin
          Inc(count);
        end;
      doCOUNT:
        begin
          if (not TmpVal.IsNull) then
            Inc(count);
        end;
      doSUM:
        begin
          if (not TmpVal.IsNull) then
            begin
              // If not numeric type (string for example) - raise
              if (not TmpVal.IsNumericDataType) then
                raise EABSException.Create(30132, ErrorGNotNumericDataType, ['SUM']);
              // if value not null
              if (Value.IsNull) then
                Value.Assign(TmpVal)
              else
                Value.Add(TmpVal);
            end;
        end;
      doAVG:
        begin
          if (not TmpVal.IsNull) then
            begin
              // If not numeric type (string for example) - raise
              if (not TmpVal.IsNumericDataType) then
                raise EABSException.Create(30133, ErrorGNotNumericDataType, ['AVG']);
              if (AvgSum.IsNull) then
                begin
                  AvgSum.Assign(TmpVal);
                  if (AvgSum.DataType <> bftDouble) then
                    AvgSum.Cast(bftDouble);
                end
              else
                AvgSum.Add(TmpVal);
              inc(count);
            end;
        end;
      else
        raise EABSException.Create(30134, ErrorGUnknownOperator,
                                   [GetOperatorName(Operator)]);
    end;
  finally
    TmpVal.Free;
  end;
end;//Accumulate


//------------------------------------------------------------------------------
// SetCountAll
//------------------------------------------------------------------------------
procedure TABSExprNodeAggregated.SetCountAll(RecordCount: Integer);
begin
  count := RecordCount;
end;// SetCountAll


//------------------------------------------------------------------------------
// check if agregate expr, const or only grouped fields are used
//------------------------------------------------------------------------------
function TABSExprNodeAggregated.CanBeUsedInGroupBy(GroupByFields: string): Boolean;
begin
  Result := True;
end;// CanBeUsedInGroupBy



////////////////////////////////////////////////////////////////////////////////
//
// TABSExprNodeCast
//
////////////////////////////////////////////////////////////////////////////////


//------------------------------------------------------------------------------
// Constructor
//------------------------------------------------------------------------------
constructor TABSExprNodeCast.Create(Node:         TABSExprNode;
                                    CastType:     TABSAdvancedFieldType;
                                    CastSizeNode: TABSExprNode = nil);
begin
  if CastSizeNode = nil then
    inherited Create(doCAST, Node)
  else
    inherited Create(doCAST, Node, CastSizeNode);
  LCastSizeNode := CastSizeNode;
  FCastType := CastType;
end;//Create


//------------------------------------------------------------------------------
// Return Data Value
//------------------------------------------------------------------------------
function TABSExprNodeCast.GetDataValue: TABSVariant;
var
  Size: Integer;
begin
  Value.Assign(TABSExprNode(Children[0]).GetDataValue);
  if IsStringFieldType(FCastType) then
    begin
      Value.Cast(AdvancedFieldTypeToBaseFieldType(FCastType));
      
      if (LCastSizeNode <> nil) then
        Size := LCastSizeNode.GetDataValue.AsInteger
      else
        Size := DEFAULT_BLOB_TO_STRING_FIELD_SIZE;

      if IsWideStringFieldType(FCastType) then
        begin
          if (Value.StrLen > Size) then
            Value.AsWideString := Copy(Value.AsWideString, 1, Size);
        end
      else
        begin
          if (Value.StrLen > Size) then
            Value.AsAnsiString := Copy(Value.AsAnsiString, 1, Size);
        end;

    end
  else
    Value.Cast(AdvancedFieldTypeToBaseFieldType(FCastType));
  Result := Value;
end;//GetDataValue


//------------------------------------------------------------------------------
// return Type of Data
//------------------------------------------------------------------------------
function TABSExprNodeCast.GetDataType: TABSAdvancedFieldType;
begin
  Result := FCastType;
end;//GetDataType


//------------------------------------------------------------------------------
// return Size of Data
//------------------------------------------------------------------------------
function TABSExprNodeCast.GetDataSize: Integer;
var
  child: TABSExprNode;
begin
  if LCastSizeNode <> nil then
    begin
      Result := LCastSizeNode.GetDataValue.AsInteger;
    end
  else
    begin
      Result := 0;
      if IsStringFieldType(GetDataType) then
        begin
          child := TABSExprNode(Children[0]);
          case child.getDataType of
            aftChar,
            aftString,
            aftWideChar,
            aftWideString:      Result := child.getDataSize;

            aftShortint:        Result := 4;  // = bftSignedInt8
            aftSmallint:        Result := 6;  // = bftSignedInt16
            aftInteger:         Result := 11; // = bftSignedInt32
            aftLargeint:        Result := 20; // = bftSignedInt64
            aftByte:            Result := 3;  // = bftUnsignedInt8
            aftWord:            Result := 5;  // = bftUnsignedInt16
            aftCardinal:        Result := 10; // = bftUnsignedInt32

            aftAutoInc:         Result := 11; // = bftSignedInt32
            aftAutoIncShortint: Result := 4;  // = bftSignedInt8
            aftAutoIncSmallint: Result := 6;  // = bftSignedInt16
            aftAutoIncInteger:  Result := 11; // = bftSignedInt32
            aftAutoIncLargeint: Result := 20; // = bftSignedInt64
            aftAutoIncByte:     Result := 3;  // = bftUnsignedInt8
            aftAutoIncWord:     Result := 5;  // = bftUnsignedInt16
            aftAutoIncCardinal: Result := 10; // = bftUnsignedInt32

            
            aftSingle:          Result := 26; // = bftSingle
            aftDouble:          Result := 26; // = bftDouble
            aftExtended:        Result := 26; // = bftExtended

            aftBoolean:         Result := 5;  // = bftLogical

            aftCurrency:        Result := 21; // = bftCurrency

            aftDate:            Result := 26; // = bftDate
            aftTime:            Result := 26; // = bftTime
            aftDateTime:        Result := 26; // = bftDateTime
            aftTimeStamp:       Result := 26; // = bftDateTime

            aftBytes:           Result := child.getDataSize; // = bftBytes
            aftVarBytes:        Result := child.getDataSize; // = bftVarBytes

            aftBlob:            Result := child.getDataSize; // = bftBlob
            aftGraphic:         Result := child.getDataSize; // = bftBlob
            aftMemo:            Result := DEFAULT_BLOB_TO_STRING_FIELD_SIZE; // = bftClob

            aftFormattedMemo:   Result := DEFAULT_BLOB_TO_STRING_FIELD_SIZE; // = bftClob
            aftWideMemo:        Result := DEFAULT_BLOB_TO_STRING_FIELD_SIZE; // = bftWideClob
            aftGuid:            Result := 38; // = bftChar(38)
            else
              raise EABSException.Create(30466, ErrorGUnsupportedDataType,
                                         [AftToStr(child.getDataType)]);
          end;
        end;
    end;
end;//GetDataSize



////////////////////////////////////////////////////////////////////////////////
//
// TABSExprCase
//
////////////////////////////////////////////////////////////////////////////////


//------------------------------------------------------------------------------
// CreateInit
//------------------------------------------------------------------------------
procedure TABSExprNodeCase.CreateInit;
begin
  FInputExpression := nil;
  FWhenExpressions := TList.Create;
  FResultExpressions := TList.Create;
  FElseResultExpression := nil;
  FIsCoalesceExpression := false;
end;//CreateInit


//------------------------------------------------------------------------------
// Create Simple Case
//------------------------------------------------------------------------------
constructor TABSExprNodeCase.CreateSimpleCase(InputExpression: TABSExprNode);
begin
  inherited Create;
  CreateInit;
  FInputExpression := InputExpression;
  Children.Add(FInputExpression);
end;//CreateSimpleCase


//------------------------------------------------------------------------------
// Create Searched Case
//------------------------------------------------------------------------------
constructor TABSExprNodeCase.CreateSearchedCase;
begin
  inherited Create;
  CreateInit;
end;//CreateSearchedCase


//------------------------------------------------------------------------------
// CreateCoalesce
//------------------------------------------------------------------------------
constructor TABSExprNodeCase.CreateCoalesce(Nodes: TABSExprNodeArray);
var
  i: Integer;
begin
  inherited Create;
  CreateInit;
  FIsCoalesceExpression := True;
  FCoalesceNodes := Nodes;
  for i:=0 to Length(FCoalesceNodes)-1 do
    Children.Add(FCoalesceNodes[i]);
end;// CreateCoalesce


//------------------------------------------------------------------------------
// Destructor
//------------------------------------------------------------------------------
destructor TABSExprNodeCase.Destroy;
begin
  FWhenExpressions.Free;
  FResultExpressions.Free;
  inherited;
end;//Destroy


//------------------------------------------------------------------------------
// return Type of Data
//------------------------------------------------------------------------------
function TABSExprNodeCase.GetDataType: TABSAdvancedFieldType;
var
  i: Integer;
begin
  Result := aftUnknown;
  if (FIsCoalesceExpression) then
    begin
      Result := FCoalesceNodes[0].GetDataType;
      for i:=1 to Length(FCoalesceNodes)-1 do
       Result := GetCommonDataType(Result, FCoalesceNodes[i].GetDataType);
    end
  else
    begin
      if FResultExpressions.Count > 0 then
        Result := TABSExprNode(FResultExpressions[0]).GetDataType;

      for i:=1 to FResultExpressions.Count-1 do
       Result := GetCommonDataType(Result, TABSExprNode(FResultExpressions[i]).GetDataType);
      if (FElseResultExpression <> nil) then
       Result := GetCommonDataType(Result, FElseResultExpression.GetDataType);
    end;
end;//GetDataType


//------------------------------------------------------------------------------
// return Size of Data
//------------------------------------------------------------------------------
function TABSExprNodeCase.GetDataSize: Integer;
var i,j: Integer;
    DType, CommonType: TABSAdvancedFieldType;
    Cast: TABSExprNodeCast;
begin
  Result := 0;

  CommonType := GetDataType;

  if (FIsCoalesceExpression) then
    begin
      // Add "Cast to Common Type" Nodes
      for i:=0 to Length(FCoalesceNodes)-1 do
       begin
        DType := FCoalesceNodes[i].GetDataType;
        if (DType <> CommonType) and (not (FCoalesceNodes[i] is TABSExprNodeCast)) then
          begin
            Cast := TABSExprNodeCast.Create(FCoalesceNodes[i], CommonType);
            for j:=0 to Children.Count-1 do
             if Children[j] = FCoalesceNodes[i] then
              begin
               Children[j] := Cast;
               Break;
              end;
            FCoalesceNodes[i] := Cast;
          end;
       end;
      // Get max DataSize
      for i:=0 to Length(FCoalesceNodes)-1 do
       Result := Max(Result, FCoalesceNodes[i].GetDataSize);
    end
  else
    begin
      // Add "Cast to Common Type" Nodes
      for i:=0 to FResultExpressions.Count-1 do
       begin
        DType := TABSExprNode(FResultExpressions[i]).GetDataType;
        if (DType <> CommonType) and (not (TABSExprNode(FResultExpressions[i]) is TABSExprNodeCast)) then
         begin
          Cast := TABSExprNodeCast.Create(TABSExprNode(FResultExpressions[i]), CommonType);
          for j:=0 to Children.Count-1 do
           if Children[j] = FResultExpressions[i] then
            begin
             Children[j] := Cast;
             Break;
            end;
          FResultExpressions[i] := Cast;
         end;
       end;

      // and FElseResultExpression
      if (FElseResultExpression <> nil) then
       begin
        DType := FElseResultExpression.GetDataType;
        if (DType <> CommonType) and (not (FElseResultExpression is TABSExprNodeCast)) then
         begin
          Cast := TABSExprNodeCast.Create(FElseResultExpression, CommonType);
          for j:=0 to Children.Count-1 do
           if Children[j] = FElseResultExpression then
            begin
             Children[j] := Cast;
             Break;
            end;
          FElseResultExpression := Cast;
         end;
       end;

      // Get max DataSize
      for i:=0 to FResultExpressions.Count-1 do
       Result := Max(Result, TABSExprNode(FResultExpressions[i]).GetDataSize);
      if (FElseResultExpression <> nil) then
      Result := Max(Result, FElseResultExpression.GetDataSize);
    end;
end;//GetDataSize


//------------------------------------------------------------------------------
// Return Data Value
//------------------------------------------------------------------------------
function TABSExprNodeCase.GetDataValue: TABSVariant;
var
  i: Integer;
  InpVal: TABSVariant;
  bValueIsSet: Boolean;
begin
  Value.SetNull;
  if (FIsCoalesceExpression) then
    begin
      for i:=0 to Length(FCoalesceNodes)-1 do
        begin
          Value.Assign(FCoalesceNodes[i].GetDataValue, False);
          if (not Value.IsNull) then break;
        end;
    end
  else
    begin
      bValueIsSet := false;
      if FInputExpression <> nil then
       begin
        // Simple Case
        InpVal := FInputExpression.GetDataValue;
        for i:=0 to FWhenExpressions.Count-1 do
         if (InpVal.Compare(TABSExprNode(FWhenExpressions[i]).GetDataValue, False) = cmprEqual) then
          begin
           Value.Assign(TABSExprNode(FResultExpressions[i]).GetDataValue, False);
           bValueIsSet := true;
           Break;
          end;
       end
      else
       begin
        // Searched Case
        for i:=0 to FWhenExpressions.Count-1 do
         if TABSExprNode(FWhenExpressions[i]).GetDataValue.AsBoolean then
          begin
           Value.Assign(TABSExprNode(FResultExpressions[i]).GetDataValue, False);
           bValueIsSet := true;
           Break;
          end;
       end;

       if(not bValueIsSet) then
         if (FElseResultExpression <> nil) then
          Value.Assign(FElseResultExpression.GetDataValue, False);
    end;

  Result := Value;
end;//GetDataValue


//------------------------------------------------------------------------------
// AddResultExpression
//------------------------------------------------------------------------------
procedure TABSExprNodeCase.AddResultExpression(Node: TABSExprNode);
begin
  FResultExpressions.Add(Node);
  Children.Add(Node);
end;//AddResultExpression


//------------------------------------------------------------------------------
// AddWhenExpression
//------------------------------------------------------------------------------
procedure TABSExprNodeCase.AddWhenExpression(Node: TABSExprNode);
begin
  FWhenExpressions.Add(Node);
  Children.Add(Node);
end;//AddWhenExpression


//------------------------------------------------------------------------------
// SetElseResultExpression
//------------------------------------------------------------------------------
procedure TABSExprNodeCase.SetElseResultExpression(Node: TABSExprNode);
begin
  FElseResultExpression := Node;
  Children.Add(Node);
end;//SetElseResultExpression



////////////////////////////////////////////////////////////////////////////////
//
// TABSExprNodeBlob
//
////////////////////////////////////////////////////////////////////////////////


//------------------------------------------------------------------------------
// MimeToBin
//------------------------------------------------------------------------------
procedure TABSExprNodeBlob.MimeToBin;
var
  MimeCoder: TStringFormat_MIME64;
  s: AnsiString;
begin
  MimeCoder := TStringFormat_MIME64.Create;
  try
    s := TABSExprNodeConst(Children[0]).getDataValue.AsAnsiString;
    s := MimeCoder.ToStr(PAnsiChar(s), Length(s));
    Value.SetData(TAbsPByte(s), Length(s), bftBlob);
  finally
    MimeCoder.Free;
  end;
end;//MimeToBin


//------------------------------------------------------------------------------
// GetDataSize
//------------------------------------------------------------------------------
function TABSExprNodeBlob.GetDataSize: Integer;
begin
  Result := 0;
end;//GetDataSize


//------------------------------------------------------------------------------
// GetDataType
//------------------------------------------------------------------------------
function TABSExprNodeBlob.GetDataType: TABSAdvancedFieldType;
begin
  Result := aftString;
end;//GetDataType


//------------------------------------------------------------------------------
// GetDataValue
//------------------------------------------------------------------------------
function TABSExprNodeBlob.GetDataValue: TABSVariant;
begin
  MimeToBin;
  Result := Value;
end;//GetDataValue


////////////////////////////////////////////////////////////////////////////////
//
// TABSExprNodeDateFunction
//
////////////////////////////////////////////////////////////////////////////////


//------------------------------------------------------------------------------
// Constructors
//------------------------------------------------------------------------------
constructor TABSExprNodeDateFunction.Create(Op: TABSDataOperator;
  Node: TABSExprNode; FormatStr: string);
begin
  inherited Create(op, Node);
  DateFormater := TDateFormater.Create(FormatStr);
end;

constructor TABSExprNodeDateFunction.Create(Op: TABSDataOperator;
                       ExtractField: TABSDateTimeExtractField;
                       ExtractSource: TABSExprNode);
begin
  inherited Create(op, ExtractSource);
  ExtractDateTimeField := ExtractField;
  DateFormater := nil;
end;// Create


//------------------------------------------------------------------------------
// Destructor
//------------------------------------------------------------------------------
destructor TABSExprNodeDateFunction.Destroy;
begin
  if (DateFormater <> nil) then
    DateFormater.Free;
  inherited;
end;//Destroy


//------------------------------------------------------------------------------
// To Date
//------------------------------------------------------------------------------
procedure TABSExprNodeDateFunction.ToDate;
var
  TmpVal: TABSVariant;
  dt: TDateTime;
begin
  Value.SetNull(bftDateTime);
  TmpVal := TABSVariant.Create;
  try
    TmpVal.Assign(TABSExprNode(Children[0]).GetDataValue, False);
    if (not TmpVal.IsNull) then
      begin
        dt := DateFormater.ToDate(string(TmpVal.AsAnsiString));
        Value.AsTDateTime := dt;
        if (trunc(dt) = 0 ) then
          Value.Cast(bftTime);
        if (frac(dt) = 0 ) then
          Value.Cast(bftDate);
      end
  finally
    TmpVal.Free;
  end;
end;//ToDate


//------------------------------------------------------------------------------
// To String
//------------------------------------------------------------------------------
procedure TABSExprNodeDateFunction.ToString;
var
  TmpVal: TABSVariant;
begin
  Value.SetNull(bftVarchar);
  TmpVal := TABSVariant.Create;
  try
    TmpVal.Assign(TABSExprNode(Children[0]).GetDataValue, False);
    if (not TmpVal.IsNull) then
      Value.AsAnsiString := AnsiString(DateFormater.ToString(TmpVal.AsTDateTime));
  finally
    TmpVal.Free;
  end;
end;//ToString


//------------------------------------------------------------------------------
// Extract
//------------------------------------------------------------------------------
procedure TABSExprNodeDateFunction.Extract;
var
  TmpVal: TABSVariant;
  Year, Month, Day: Word;
  Hour,Min,Sec,MSec: Word;
begin
  Value.SetNull(bftUnsignedInt16);
  TmpVal := TABSVariant.Create;
  try
    TmpVal.Assign(TABSExprNode(Children[0]).GetDataValue, False);
    if (not TmpVal.IsNull) then
      begin
        if (ExtractDateTimeField in [dtefYear, dtefMonth, dtefDay]) then
          DecodeDate(TmpVal.AsTDate,Year,Month,Day)
        else
          DecodeTime(TmpVal.AsTTime,Hour,Min,Sec,MSec);
        case ExtractDateTimeField of
          dtefYear: Value.AsWord := Year;
          dtefMonth: Value.AsWord := Month;
          dtefDay: Value.AsWord := Day;
          dtefHour: Value.AsWord := Hour;
          dtefMinute: Value.AsWord := Min;
          dtefSecond: Value.AsWord := Sec;
        end;
      end;
  finally
    TmpVal.Free;
  end;
end;// Extract


//------------------------------------------------------------------------------
// Return Data Value
//------------------------------------------------------------------------------
function TABSExprNodeDateFunction.GetDataValue: TABSVariant;
begin
  case Operator of
    doSYSDATE:        Value.AsTDateTime := now;
    doCURRENT_DATE:   Value.AsTDate := now;
    doCURRENT_TIME:   Value.AsTTime := now;
    doTODATE:         ToDate;
    doTOSTRING:       ToString;
    doEXTRACT:        Extract;
   else raise EABSException.Create(30130, ErrorGUnknownOperator,
                                   [GetOperatorName(Operator)]);
  end;
  Result := Value;
end;//GetDataValue


//------------------------------------------------------------------------------
// return Type of Data
//------------------------------------------------------------------------------
function TABSExprNodeDateFunction.GetDataType: TABSAdvancedFieldType;
begin
  case Operator of
    doSYSDATE:
                    Result := aftDateTime;
    doCURRENT_DATE:
                    Result := aftDate;
    doCURRENT_TIME:
                    Result := aftTime;
    doTODATE:
                    Result := DateFormater.GetDataType;
    doTOSTRING:
                    Result := aftString;
    doEXTRACT:
                    Result := aftWord;
    else
                    raise EABSException.Create(30287, ErrorGUnknownOperator,
                                               [GetOperatorName(Operator)]);
  end;
end;//GetDataType


//------------------------------------------------------------------------------
// return Size of Data (for strings and arrays)
//------------------------------------------------------------------------------
function TABSExprNodeDateFunction.GetDataSize: Integer;
begin
  if Operator = doTOSTRING then
    Result := DateFormater.GetStringMaxSize
  else
    Result:=0
end;//GetDataSize



////////////////////////////////////////////////////////////////////////////////
//
// TABSExprNodeConst
//
////////////////////////////////////////////////////////////////////////////////


//------------------------------------------------------------------------------
// return Value
//------------------------------------------------------------------------------
function TABSExprNodeConst.GetDataValue: TABSVariant;
begin
  Result := Value;
end;//GetDataValue


//------------------------------------------------------------------------------
// return Type of Data
//------------------------------------------------------------------------------
function TABSExprNodeConst.GetDataType: TABSAdvancedFieldType;
begin
  Result := BaseFieldTypeToAdvancedFieldType(Value.DataType);
end;//GetDataType


//------------------------------------------------------------------------------
// return Size of Data (for strings and arrays)
//------------------------------------------------------------------------------
function TABSExprNodeConst.GetDataSize: Integer;
begin
  Result := 0;
  if (not Value.IsNull) then
    if (Value.IsStringDataType) then
      begin
        if (Value.IsWideStringDataType) then
          Result := Length(Value.AsWideString)*sizeof(WideChar)
        else
          Result := Length(Value.AsAnsiString);
        //Result := Value.DataSize;
        // skip last zeros
        // Dec(Result);
        (*if Value.IsWideStringDataType then
          Dec(Result);*)
      end;
end;//GetDataSize


//------------------------------------------------------------------------------
// IsLikePatternEqualToPartialComparePattern
//------------------------------------------------------------------------------
function TABSExprNodeConst.IsLikePatternEqualToPartialComparePattern: Boolean;
var
  i: Integer;
begin
  i := Value.StrLen;
  Result := (i > 1) and (Pos('%',Value.AsWideString) = i);
end;// IsLikePatternEqualToPartialComparePattern

function TABSExprNodeConst.IsLowerCase: Boolean;
begin
  Result := False;
  if Value.IsStringDataType then
    if Value.IsWideStringDataType then
      Result := Value.AsWideString =  AbsStrUtils.WideLowerCase(Value.AsWideString)
    else
      Result := string(Value.AsAnsiString) = AnsiLowerCase(string(Value.AsAnsiString));
end;

function TABSExprNodeConst.IsUpperCase: Boolean;
begin
  Result := False;
  if Value.IsStringDataType then
    if Value.IsWideStringDataType then
      Result := Value.AsWideString = AbsStrUtils.WideUpperCase(Value.AsWideString)
    else
      Result := string(Value.AsAnsiString) = AnsiUpperCase(string(Value.AsAnsiString));
end;



//------------------------------------------------------------------------------
// TruncateForPartialCompare
//------------------------------------------------------------------------------
procedure TABSExprNodeConst.TruncateForPartialCompare;
var
  i: Integer;
begin
  i := Value.StrLen;
  if Value.IsWideStringDataType then
    Value.AsWideString := Copy(Value.AsWideString,1,i-1)
  else
    Value.AsAnsiString := Copy(Value.AsAnsiString,1,i-1);
end;// TruncateForPartialCompare


//------------------------------------------------------------------------------
// can be used by the AO?
//------------------------------------------------------------------------------
function TABSExprNodeConst.CanBeAssigned(AO, ParentQueryAO: TABSAO;
                                         ParentCursor: TABSCursor; ForHavingClause: Boolean = False): Boolean;
begin
  Result := True;
end;//CanBeAssigned


////////////////////////////////////////////////////////////////////////////////
//
// TABSExprNodeBoolean
//
////////////////////////////////////////////////////////////////////////////////


//------------------------------------------------------------------------------
// Constructor
//------------------------------------------------------------------------------
constructor TABSExprNodeBoolean.Create(CaseInsensitive, PartialKey: Boolean);
begin
  inherited;
  TempVal := TABSVariant.Create(bftLogical);
  EscapeStr := '';
end;//Create


//------------------------------------------------------------------------------
// Constructor
//------------------------------------------------------------------------------
constructor TABSExprNodeBoolean.Create(Op: TABSDataOperator;
  CaseInsensitive, PartialKey: boolean; aEscapeStr: string);
begin
  Create(CaseInsensitive, PartialKey);
  Operator := Op;
  EscapeStr := aEscapeStr;
end;//Create


//------------------------------------------------------------------------------
// Constructor
//------------------------------------------------------------------------------
constructor TABSExprNodeBoolean.Create(Op: TABSDataOperator;
                     Node: TABSExprNode; CaseInsensitive, PartialKey: boolean);
begin
  Create(Op, CaseInsensitive, PartialKey);
  Children.Add(Node);
end;//Create


//------------------------------------------------------------------------------
// Constructor
//------------------------------------------------------------------------------
constructor TABSExprNodeBoolean.Create(Op: TABSDataOperator;
            Node1, Node2: TABSExprNode; CaseInsensitive, PartialKey: boolean);
begin
  Create(Op, Node1, CaseInsensitive, PartialKey);
  Children.Add(Node2);
end;//Create


//------------------------------------------------------------------------------
// Constructor
//------------------------------------------------------------------------------
constructor TABSExprNodeBoolean.Create(Op: TABSDataOperator;
       Node1, Node2, Node3: TABSExprNode;CaseInsensitive, PartialKey: boolean);
begin
  Create(Op, Node1, Node2, CaseInsensitive, PartialKey);
  Children.Add(Node3);
end;//Create


//------------------------------------------------------------------------------
// Destructor
//------------------------------------------------------------------------------
destructor TABSExprNodeBoolean.Destroy;
begin
  if (TempVal <> nil) then
    TempVal.Free;
  TempVal := nil;
  inherited;
end;//Destroy


//------------------------------------------------------------------------------
// return Value
//------------------------------------------------------------------------------
procedure TABSExprNodeBoolean.Like;
var
  Str, Pattern, Escape: TABSVariant;
  CommonType: TABSBaseFieldType;
  EscapeChar: AnsiChar;
  EscapeWideChar: WideChar;
begin
  Str := TABSVariant.Create(bftLogical);
  Pattern := TABSVariant.Create(bftLogical);
  Escape := TABSVariant.Create(bftVarchar);
  try
    Str.Assign(TABSExprNode(Children[0]).GetDataValue, False);
    Pattern.Assign(TABSExprNode(Children[1]).GetDataValue, False);
    Escape.AsWideString := EscapeStr;

    // If Nulls
    if (Pattern.IsNull{ or Str.IsNull}) then
      begin
        Exit;
      end;

    if Str.IsNull then
      Str.DataType := Pattern.DataType;

    // Get Common Data Type
    CommonType := GetCommonDataType(Str.DataType, Pattern.DataType);
    // Check for strings
    if IsStringFieldType(CommonType) then
      else raise EABSException.Create(30120, ErrorGNotStringArgument, ['LIKE']);

    // Do Like
    if IsWideStringFieldType(CommonType) then
      begin
        if (Length(Escape.AsWideString) > 0) then
          EscapeWideChar := Escape.AsWideString[1]
        else
          EscapeWideChar := WideChar(0);
        Value.AsBoolean := IsWideStrMatchPattern(
                                              PWideChar(Str.AsWideString),
                                              PWideChar(Pattern.AsWideString),
                                              Length(Escape.AsWideString) > 0,
                                              EscapeWideChar,
                                              FCaseInsensitive
                                            );
      end
    else
      begin
        if (Length(Escape.AsAnsiString) > 0) then
          EscapeChar := Escape.AsAnsiString[1]
        else
          EscapeChar := #0;

        Value.AsBoolean := IsStrMatchPattern(
                                              PAnsiChar(Str.AsAnsiString),
                                              PAnsiChar(Pattern.AsAnsiString),
                                              Length(Escape.AsAnsiString) > 0,
                                              EscapeChar,
                                              FCaseInsensitive
                                            )
      end;

  finally
    Str.Free;
    Pattern.Free;
    Escape.Free;
  end;
end;//Like


//------------------------------------------------------------------------------
// process IN (SELECT ...)
//------------------------------------------------------------------------------
procedure TABSExprNodeBoolean.bInSubquery;
var
  Val1: TABSVariant;
begin
  Val1 := TABSVariant.Create;
  try
    Val1.Assign(TABSExprNode(Children[0]).GetDataValue);
    if (TABSExprNodeSubquery(Children[1]).IsValueIn(Val1)) then
      Value.AsBoolean := True
    else
      Value.AsBoolean := False;
  finally
    Val1.Free;
  end;
end;// bInSubquery


//------------------------------------------------------------------------------
// process IN (...)
//------------------------------------------------------------------------------
procedure TABSExprNodeBoolean.bIn;
var
  Val1, Val2: TABSVariant;
  //CommonType: TABSBaseFieldType;
  i: Integer;
begin
  if (TObject(Children[1]) is TABSExprNodeSubquery) then
    bInSubquery
  else
    begin
      Val1 := TABSVariant.Create;
      Val2 := TABSVariant.Create;
      try
        Val1.Assign(TABSExprNode(Children[0]).GetDataValue);
        Value.AsBoolean := False;
        // Val is null ?
        if (Val1.IsNull) then
          Value.SetNull(bftLogical)
        else
          for i:=1 to Children.Count-1 do
            begin
              Val2.Assign(TABSExprNode(Children[i]).GetDataValue);
              if Val1.Compare(Val2) = cmprEqual then
                begin
                  Value.AsBoolean := true;
                  Break;
                end;
            end;
      finally
        Val1.Free;
        Val2.Free;
      end;
    end;
end;//bIn


//------------------------------------------------------------------------------
// process A BETWEEN B AND C
//------------------------------------------------------------------------------
procedure TABSExprNodeBoolean.Between;
var
  ValCur, ValMin, ValMax: TABSVariant;
begin
  ValCur := TABSVariant.Create;
  ValMin := TABSVariant.Create;
  ValMax := TABSVariant.Create;
  try
    ValCur.Assign(TABSExprNode(Children[0]).GetDataValue);
    ValMin.Assign(TABSExprNode(Children[1]).GetDataValue);
    ValMax.Assign(TABSExprNode(Children[2]).GetDataValue);
    // Nulls ?
    if (ValCur.IsNull or ValMin.IsNull or ValMax.IsNull) then
      Exit;
    Value.AsBoolean := False;
    if ValCur.Compare(ValMin) in [cmprEqual, cmprGreater] then
      if ValCur.Compare(ValMax) in [cmprEqual, cmprLower] then
         Value.AsBoolean := True;
  finally
    ValCur.Free;
    ValMin.Free;
    ValMax.Free;
  end;
end;//Between


//------------------------------------------------------------------------------
//process EXISTS
//------------------------------------------------------------------------------
procedure TABSExprNodeBoolean.Exists;
begin
  if (TABSExprNodeSubquery(Children[0]).Exists) then
    Value.AsBoolean := True
  else
    Value.AsBoolean := False;
end;// Exists


//------------------------------------------------------------------------------
// return Value
//------------------------------------------------------------------------------
function TABSExprNodeBoolean.GetDataValue: TABSVariant;
var
  i: Integer;
begin
  Value.SetNull(bftLogical);

   case Operator of
    doNOT: // NOT
      begin
        TempVal.Assign(TABSExprNode(Children[0]).GetDataValue);
        // NOT <null> = NULL
        if not TempVal.IsNull then
          Value.AsBoolean := not TempVal.AsBoolean;
      end;
    doAND: // AND
      begin
        // possible result is TRUE
        Value.AsBoolean := True;
        // Loop for all childs
        for i:=0 to Children.Count-1 do
         begin
          TempVal.Assign(TABSExprNode(Children[i]).GetDataValue);

          // x AND NULL = NULL (and check later)
          if TempVal.IsNull then
           begin
            Value.SetNull(bftLogical);
            //break;
            continue;
           end;

          // x AND FALSE = FALSE
          if (not TempVal.AsBoolean) then
           begin
            Value.AsBoolean := False;
            break;
           end;
         end;//for
      end;
    doOR: // OR
      begin
        // possible result is FALSE
        Value.AsBoolean := False;
        // Loop for all childs
        for i:=0 to Children.Count-1 do
         begin
          TempVal.Assign(TABSExprNode(Children[i]).GetDataValue);

          // False OR NULL = NULL (and check later)
          if TempVal.IsNull then
           begin
            Value.SetNull(bftLogical);
            continue;
           end;

          // x OR TRUE=TRUE;
          if (TempVal.AsBoolean) then
           begin
            Value.AsBoolean := True;
            break;
           end;
         end;
      end;
    doISNULL:  // IS NULL
      begin
        Value.AsBoolean := TABSExprNode(Children[0]).GetDataValue.IsNull;
      end;
    doISNOTNULL: // IS NOT NULL
      begin
        Value.AsBoolean := not TABSExprNode(Children[0]).GetDataValue.IsNull;
      end;
    doLIKE:
      begin
        Like;
      end;
    doNOTLIKE:
      begin
        Like;
        if (not Value.IsNull) then
          Value.AsBoolean := not Value.AsBoolean;
      end;
    doIN:
      begin
        bIn;
      end;
    doNOTIN:
      begin
        bIn;
        if (not Value.IsNull) then
          Value.AsBoolean := not Value.AsBoolean;
      end;
    doBETWEEN:
      begin
        Between;
      end;
    doNOTBETWEEN:
      begin
        Between;
        if (not Value.IsNull) then
          Value.AsBoolean := not Value.AsBoolean;
      end;
    doEXISTS:
      begin
        Exists;
      end;
    else
      raise EABSException.Create(30116, ErrorGUnknownOperator,
                                                   [GetOperatorName(Operator)]);
   end;
  Result := Value;
end;//GetDataValue


//------------------------------------------------------------------------------
// can use index for comparison?
//------------------------------------------------------------------------------
function TABSExprNodeBoolean.CanUseIndex(ColumnNo: Integer; ConditionSearchOperator: TABSSearchCondition;
 IndexDef: TABSIndexDef; SessionID: TABSSessionID = INVALID_SESSION_ID): Boolean;
var
  FieldNode: TABSExprNodeField;
begin
  if (Operator in [doISNULL, doISNOTNULL]) then
    begin
      if (CanUseNodeAsField(Children[0])) then
        begin
          FieldNode := Children[0];
          Result := (UpperCase(FieldNode.FieldName) = UpperCase(IndexDef.Columns[ColumnNo].FieldName));
          if (Result) then
            if (ColumnNo = 0) then
              Result := (not IndexDef.Temporary) or (IndexDef.SessionID = SessionID);
          if (Result) then
            if (ColumnNo > 0) then
              Result := ((Operator = doISNULL) and (ConditionSearchOperator = scEqual));
        end;
    end
  else
    Result := False;
end;// CanUseIndex


//------------------------------------------------------------------------------
// add index scan condition
//------------------------------------------------------------------------------
procedure TABSExprNodeBoolean.AddAsIndexScanCondition(
               ScanConditions:          TABSScanSearchConditionArray;
               IndexDef:                TABSIndexDef;
               Expression:              TABSExpression
                          );
var
  FieldNode:     TABSExprNodeField;
  ScanCondition: TABSScanSearchCondition;
  NullValue:         TABSVariant;
begin
  FieldNode := Children[0];
  if (FieldNode.LCursor = nil) then
    raise EABSException.Create(20254, ErrorANilPointer);
  if (IndexDef.ColumnCount < 1) then
    raise EABSException.Create(20255, ErrorAInvalidIndexForSearch);

  ScanCondition := TABSScanSearchCondition.Create(False);
  NullValue := TABSVariant.Create;
  try
    if (IndexDef.Columns[0].Descending) then
      case Operator of
       doISNULL: ScanCondition.Condition := scEqual;
       doISNOTNULL: ScanCondition.Condition := scLower;
      end
    else
      case Operator of
       doISNULL: ScanCondition.Condition := scEqual;
       doISNOTNULL: ScanCondition.Condition := scGreater;
      end;

    ScanCondition.KeyRecordBuffer := FieldNode.LCursor.AllocateRecordBuffer;
    ScanCondition.KeyRecordBufferSize := FieldNode.LCursor.RecordBufferSize;
    ScanCondition.KeyFieldCount := 0;
    ScanCondition.IndexID := IndexDef.ObjectID;
    ScanCondition.Expression := nil;
    ScanCondition.CreatedFromExpression := True;
    NullValue.SetNull;
    FillScanCondition(NullValue, FieldNode.LCursor, 0, FieldNode.FieldNo, IndexDef,Expression, ScanCondition);
    ScanConditions.AddCondition(ScanCondition);
  finally
    NullValue.Free;
    ScanCondition.Free;
  end;
end;// AddScanCondition


//------------------------------------------------------------------------------
// add checks to multi-field index scan condition
//------------------------------------------------------------------------------
procedure TABSExprNodeBoolean.ExtendIndexScanCondition(
               ScanCondition: TABSScanSearchCondition;
               IndexDef:      TABSIndexDef;
               Expression:    TABSExpression);
var
  FieldNode:     TABSExprNodeField;
  NullValue:         TABSVariant;
begin
  FieldNode := Children[0];
  NullValue.SetNull;
  FillScanCondition(NullValue, FieldNode.LCursor, ScanCondition.KeyFieldCount, FieldNode.FieldNo, IndexDef, Expression, ScanCondition);
end;// ExtendIndexScanCondition




////////////////////////////////////////////////////////////////////////////////
//
// TABSExprNodeComparison
//
////////////////////////////////////////////////////////////////////////////////


//------------------------------------------------------------------------------
// Constructor
//------------------------------------------------------------------------------
constructor TABSExprNodeComparison.Create(Op: TABSDataOperator; Node1,
  Node2: TABSExprNode; TrueFalseNullLogic, CaseInsensitive,
  PartialKey: boolean);
begin
  inherited Create(Op, Node1, Node2,CaseInsensitive, PartialKey);
  F3ValueLogic := TrueFalseNullLogic;
  Value.SetNull(bftLogical);
  FQuantifier := qUNDEFINED;
end;//Create


//------------------------------------------------------------------------------
// Constructor
//------------------------------------------------------------------------------
constructor TABSExprNodeComparison.Create(Op: TABSDataOperator;
   Quantifier: TABSQuantifier;Node1, Node2: TABSExprNode;
   TrueFalseNullLogic, CaseInsensitive, PartialKey: boolean);
begin
  Create(Op, Node1, Node2, TrueFalseNullLogic, CaseInsensitive, PartialKey);
  FQuantifier := Quantifier;
end;// Create


//------------------------------------------------------------------------------
// converts some WideStrings to Strings
//------------------------------------------------------------------------------
procedure TABSExprNodeComparison.PatchWideStrings;
var
   i:      Integer;
   NField: TABSExprNodeField;
   NConst: TABSExprNodeConst;
   dt: TABSAdvancedFieldType;
begin
  NField := nil;
  NConst := nil;
  for i:=0 to Children.Count-1 do
    if TABSExprNode(Children[i]) is TABSExprNodeField then
      NField := Children[i]
    else if TABSExprNode(Children[i]) is TABSExprNodeConst then
      NConst := Children[i];

  if (NConst <> nil) and (NField <> nil) then
    begin
      dt := NField.GetDataType;
      if ( IsStringFieldType(dt) and (not IsWideStringFieldType(dt)) ) then
        if IsWideStringFieldType(NConst.GetDataType) then
          NConst.Value.Cast(bftVarchar);
    end;
end;//PatchWideStrings


//------------------------------------------------------------------------------
// x > y
//------------------------------------------------------------------------------
function TABSExprNodeComparison.GetDataValueByCompare: TABSVariant;
var
  cmpRes: TABSCompareResult;
  arg1, arg2: TABSVariant;
begin
    arg1 := TABSExprNode(Children[0]).GetDataValue;
    arg2 := TABSExprNode(Children[1]).GetDataValue;
    cmpRes := arg1.Compare(arg2, F3ValueLogic , FCaseInsensitive, FPartialKey);

    case Operator of
     doEQ:   Value.AsBoolean := (cmpRes = cmprEqual);
     doNE:   if (F3ValueLogic) then
               Value.AsBoolean := (cmpRes in [cmprGreater, cmprLower])
             else
               Value.AsBoolean := (cmpRes <> cmprEqual);
     doLT:   Value.AsBoolean := (cmpRes = cmprLower);
     doGT:   Value.AsBoolean := (cmpRes = cmprGreater);
     doLE:   Value.AsBoolean := (cmpRes in [cmprEqual, cmprLower]);
     doGE:   Value.AsBoolean := (cmpRes in [cmprEqual, cmprGreater]);
     else
       raise EABSException.Create(30117, ErrorGUnknownOperator, [GetOperatorName(Operator)]);
    end;
  Result := Value;
end;// GetDataValueByCompare


//------------------------------------------------------------------------------
// x > all (select ...)
//------------------------------------------------------------------------------
function TABSExprNodeComparison.GetDataValueByQuantifiedCompare: TABSVariant;
var
  arg1: TABSVariant;
begin
  arg1 := TABSExprNode(Children[0]).GetDataValue;
  Value.AsBoolean := TABSExprNodeSubquery(Children[1]).QuantifiedCompare(Operator, FQuantifier, arg1);
  Result := Value;
end;// GetDataValueByQuantifiedCompare


//------------------------------------------------------------------------------
// return Value
//------------------------------------------------------------------------------
function TABSExprNodeComparison.GetDataValue: TABSVariant;
begin
  if (FQuantifier = qUNDEFINED) then
    Result := GetDataValueByCompare
  else
    Result := GetDataValueByQuantifiedCompare;
end;//GetDataValue


//------------------------------------------------------------------------------
// GetFieldAndConstNodes
//------------------------------------------------------------------------------
procedure TABSExprNodeComparison.GetFieldAndConstNodes(var FieldNode: TABSExprNodeField; var ConstNode: TABSExprNodeConst);
begin
  FieldNode := nil;
  ConstNode := nil;

  if (CanUseNodeAsConst(Children[0])) then
    ConstNode := Children[0];
  if (CanUseNodeAsConst(Children[1])) then
    ConstNode := Children[1];

  if (CanUseNodeAsField(Children[0])) then
    if (TObject(Children[0]) is TABSExprNodeField) then
      FieldNode := Children[0]
    else
      begin
        // Upper(Field) or Lower(Field)
        if  (TObject(Children[0]) is TABSExprNodeStringFunction) and Assigned(ConstNode) then
          if ((TABSExprNodeStringFunction(Children[0]).Operator = doUPPER) and not ConstNode.IsUpperCase) or
             ((TABSExprNodeStringFunction(Children[0]).Operator = doLOWER) and not ConstNode.IsLowerCase) then
          begin
            FieldNode := nil;
            ConstNode := nil;
            Exit;
          end
          else
            if (TABSExprNodeStringFunction(Children[0]).Operator in [doUPPER, doLOWER]) then
              FCaseInsensitive := True;

        FieldNode := TABSExprNode(Children[0]).Children[0];

      end;
  if (CanUseNodeAsField(Children[1])) then
    if (TObject(Children[1]) is TABSExprNodeField) then
      FieldNode := Children[1]
    else
      begin
        // Upper(Field) or Lower(Field)
        if  (TObject(Children[1]) is TABSExprNodeStringFunction) and Assigned(ConstNode) then
          if ((TABSExprNodeStringFunction(Children[1]).Operator = doUPPER) and not ConstNode.IsUpperCase) or
             ((TABSExprNodeStringFunction(Children[1]).Operator = doLOWER) and not ConstNode.IsLowerCase) then
          begin
            FieldNode := nil;
            ConstNode := nil;
            Exit;
          end
          else
            if (TABSExprNodeStringFunction(Children[1]).Operator in [doUPPER, doLOWER]) then
              FCaseInsensitive := True;
        FieldNode := TABSExprNode(Children[1]).Children[0];
      end;


end;// GetFieldAndConstNodes


//------------------------------------------------------------------------------
// can use index for comparison?
//------------------------------------------------------------------------------
function TABSExprNodeComparison.CanUseIndex(IndexColumnNo: Integer; ConditionSearchOperator: TABSSearchCondition;
 IndexDef: TABSIndexDef; SessionID: TABSSessionID = INVALID_SESSION_ID): Boolean;
var
  FieldNode: TABSExprNodeField;
  ConstNode: TABSExprNodeConst;
begin
  GetFieldAndConstNodes(FieldNode,ConstNode);
  if ((FieldNode <> nil) and (ConstNode <> nil)) then
   begin
     Result := (UpperCase(FieldNode.FieldName) =
                UpperCase(IndexDef.Columns[IndexColumnNo].FieldName));
     if (Result) then
       if (IndexColumnNo = 0) then
           Result := (not IndexDef.Temporary) or (IndexDef.SessionID = SessionID);
     if (Result) then
      if (IsBlobFieldType(FieldNode.BaseFieldType)) then
        Result := False;
     if (Result) then
      if (IsStringFieldType(FieldNode.BaseFieldType)) then
       Result := (FCaseInsensitive = IndexDef.Columns[IndexColumnNo].CaseInsensitive);
// 5.07 uncommented code
     if (Result) then
       Result := (FPartialKey = False);
// end uncommented code
     if (Result) then
       Result := (Operator in [doEQ,doGT,doLT,doGE,doLE]);
     if (Result) then
       if (IsStringFieldType(FieldNode.BaseFieldType)) then
         // 5.07
         // old code
         // Result := (FieldNode.FieldSize >= ConstNode.GetDataSize);
         // new code
         Result := (FieldNode.FieldSize > ConstNode.GetDataSize);
     if (Result) then
       if (IndexColumnNo > 0) then
         Result := ((ConditionSearchOperator = scEqual) and (Operator = doEQ));
   end
  else
   Result := False;
end;// CanUseIndex


//------------------------------------------------------------------------------
// add index scan condition
//------------------------------------------------------------------------------
procedure TABSExprNodeComparison.AddAsIndexScanCondition(
               ScanConditions:          TABSScanSearchConditionArray;
               IndexDef:                TABSIndexDef;
               Expression:              TABSExpression);
var
  FieldNode:     TABSExprNodeField;
  ConstNode:     TABSExprNodeConst;
  ScanCondition: TABSScanSearchCondition;
begin
  GetFieldAndConstNodes(FieldNode,ConstNode);
  if ((FieldNode <> nil) and (ConstNode <> nil)) then
   begin
    if (FieldNode.LCursor = nil) then
      raise EABSException.Create(20054, ErrorANilPointer);
    if (IndexDef.ColumnCount < 1) then
      raise EABSException.Create(20057, ErrorAInvalidIndexForSearch);

    ScanCondition := TABSScanSearchCondition.Create(False);
    try
      if (IndexDef.Columns[0].Descending) then
        case Operator of
         doEQ: ScanCondition.Condition := scEqual;
         doGT: ScanCondition.Condition := scLower;
         doLT: ScanCondition.Condition := scGreater;
         doGE: ScanCondition.Condition := scLowerEqual;
         doLE: ScanCondition.Condition := scGreaterEqual;
        end
      else
        case Operator of
         doEQ: ScanCondition.Condition := scEqual;
         doGT: ScanCondition.Condition := scGreater;
         doLT: ScanCondition.Condition := scLower;
         doGE: ScanCondition.Condition := scGreaterEqual;
         doLE: ScanCondition.Condition := scLowerEqual;
        end;

      ScanCondition.KeyRecordBuffer := FieldNode.LCursor.AllocateRecordBuffer;
      ScanCondition.KeyRecordBufferSize := FieldNode.LCursor.RecordBufferSize;
      ScanCondition.KeyFieldCount := 0;
      ScanCondition.IndexID := IndexDef.ObjectID;
      ScanCondition.Expression := nil;
      ScanCondition.CreatedFromExpression := True;
      ScanCondition.PartialCompare := FPartialKey;
      FillScanCondition(ConstNode.GetDataValue, FieldNode.LCursor, 0, FieldNode.FieldNo, IndexDef, Expression, ScanCondition);
      ScanConditions.AddCondition(ScanCondition);
    finally
      ScanCondition.Free;
    end;
   end
  else
   raise EABSException.Create(20055, ErrorANilPointer);
end;// AddAsIndexScanCondition


// 5.08
//------------------------------------------------------------------------------
// get field node as is or from UPPER(field_node)
//------------------------------------------------------------------------------
function TABSExprNodeComparison.GetFieldNode(Node: TABSExprNode): TAbsExprNodeField;
begin
    if (Node is TABSExprNodeStringFunction) then
        Result := TABSExprNodeStringFunction(Node).Children[0]
    else
        Result := Node as TAbsExprNodeField;
end;


//------------------------------------------------------------------------------
// add checks to multi-field index scan condition
//------------------------------------------------------------------------------
procedure TABSExprNodeComparison.ExtendIndexScanCondition(
               ScanCondition: TABSScanSearchCondition;
               IndexDef:      TABSIndexDef;
               Expression:    TABSExpression);


var
  FieldNode:     TABSExprNodeField;
  ConstNode:     TABSExprNode;
begin
  FieldNode := nil;
  ConstNode := nil;
  if (CanUseNodeAsField(Children[0])) then
    FieldNode := GetFieldNode(Children[0]);
  if (CanUseNodeAsField(Children[1])) then
    FieldNode := GetFieldNode(Children[1]);
  if (CanUseNodeAsConst(Children[0])) then
    ConstNode := Children[0];
  if (CanUseNodeAsConst(Children[1])) then
    ConstNode := Children[1];
  if ((FieldNode <> nil) and (ConstNode <> nil)) then
    FillScanCondition(ConstNode.GetDataValue, FieldNode.LCursor, ScanCondition.KeyFieldCount, FieldNode.FieldNo, IndexDef, Expression, ScanCondition);
end;// ExtendIndexScanCondition


//------------------------------------------------------------------------------
// is node a join condition?
//------------------------------------------------------------------------------
function TABSExprNodeComparison.IsJoinCondition(AO1,
  AO2: TABSAO): Boolean;
var
  Left, Right: TABSExprNodeField;
  FieldNumber: TABSIntegerArray;
  Field1LeftCount, Field1RightCount: integer;
  Field2LeftCount, Field2RightCount: integer;
begin
  Result := false;
  if not (CanUseNodeAsField(Children[0]) and CanUseNodeAsField(Children[1])) then
    Exit;

  Left := GetFieldNode(Children[0]);
  if (Left <> Children[0]) then
   Exit;
  Right := GetFieldNode(Children[1]);
  if (Right <> Children[1]) then
   Exit;
  if Operator = doEQ then
   if ((Left is TABSExprNodeField) and
       (Right is TABSExprNodeField)) then
    begin
     FieldNumber := TABSIntegerArray.Create;
     try
      // map fields
      FieldNumber.SetSize(0);
      Field1LeftCount := AO1.FieldExists(Left.FieldName, Left.TableName, True, FieldNumber);
      FieldNumber.SetSize(0);
      Field2LeftCount := AO1.FieldExists(Right.FieldName, Right.TableName, True, FieldNumber);
      FieldNumber.SetSize(0);
      Field1RightCount := AO2.FieldExists(Left.FieldName, Left.TableName, True, FieldNumber);
      FieldNumber.SetSize(0);
      Field2RightCount := AO2.FieldExists(Right.FieldName, Right.TableName, True, FieldNumber);
      // ambiguous field reference?
      if (Field1LeftCount+Field1RightCount > 1) then
        raise EABSException.Create(10404, ErrorLAmbiguousFieldReference, [Left.FieldName]);
      if (Field2LeftCount+Field2RightCount > 1) then
        raise EABSException.Create(10405, ErrorLAmbiguousFieldReference, [Right.FieldName]);
      // each field macths only 1 AO?
      if ((Field1LeftCount+Field1RightCount = 1) and
          (Field2LeftCount+Field2RightCount = 1)) then
       // boths fields match different AO?
       if (Field1LeftCount <> Field2LeftCount) then
        Result := True;
     finally
      FieldNumber.Free;
     end;
    end;
end; // IsJoinCondition


////////////////////////////////////////////////////////////////////////////////
//
// TABSExprNodeArithmetic
//
////////////////////////////////////////////////////////////////////////////////


//------------------------------------------------------------------------------
// ADD operation
//------------------------------------------------------------------------------
procedure TABSExprNodeArithmetic.AddData;
var
  i: Integer;
begin
  // Get first argument
  Value.Assign(TABSExprNode(Children[0]).GetDataValue);
  // Add next values
  for i:=1 to Children.Count-1 do
    Value.Add(TABSExprNode(Children[i]).GetDataValue);
end;//AddData


//------------------------------------------------------------------------------
// SUB operation
//------------------------------------------------------------------------------
procedure TABSExprNodeArithmetic.SubData;
begin
  Value.Assign(TABSExprNode(Children[0]).GetDataValue);
  Value.InvertValue;
end;//SubData


//------------------------------------------------------------------------------
// MUL operation
//------------------------------------------------------------------------------
procedure TABSExprNodeArithmetic.MulData;
var
  i: Integer;
begin
  // Get first argument
  Value.Assign(TABSExprNode(Children[0]).GetDataValue);
  // Mul next values
  for i:=1 to Children.Count-1 do
    Value.Mul(TABSExprNode(Children[i]).GetDataValue);
end;//MulData


//------------------------------------------------------------------------------
// DIV operation
//------------------------------------------------------------------------------
procedure TABSExprNodeArithmetic.DivData;
begin
  // Get first argument
  Value.Assign(TABSExprNode(Children[0]).GetDataValue);
  Value.Division(TABSExprNode(Children[1]).GetDataValue);
end;//DivData


//------------------------------------------------------------------------------
// ABS operation
//------------------------------------------------------------------------------
procedure TABSExprNodeArithmetic.GetABSValue;
var
  v: Variant;
begin
  v := TABSExprNode(Children[0]).GetDataValue.AsVariant;
  if (v < 0) then
    Value.AsVariant := -v
  else
    Value.AsVariant := v;
end;// ABS operation


//------------------------------------------------------------------------------
// ACOS operation
//------------------------------------------------------------------------------
procedure TABSExprNodeArithmetic.GetACOSValue;
begin
  Value.AsDouble := ArcCos(TABSExprNode(Children[0]).GetDataValue.AsDouble);
end;// GetACOSValue


//------------------------------------------------------------------------------
// ASIN operation
//------------------------------------------------------------------------------
procedure TABSExprNodeArithmetic.GetASINValue;
begin
  Value.AsDouble := ArcSin(TABSExprNode(Children[0]).GetDataValue.AsDouble);
end;// GetASINValue


//------------------------------------------------------------------------------
// ATAN operation
//------------------------------------------------------------------------------
procedure TABSExprNodeArithmetic.GetATANValue;
begin
  Value.AsDouble := ArcTan(TABSExprNode(Children[0]).GetDataValue.AsDouble);
end;// GetATANValue


//------------------------------------------------------------------------------
// CEIL operation
//------------------------------------------------------------------------------
procedure TABSExprNodeArithmetic.GetCEILValue;
begin
  Value.AsInteger := Ceil(TABSExprNode(Children[0]).GetDataValue.AsDouble);
end;// GetCEILValue


//------------------------------------------------------------------------------
// COS operation
//------------------------------------------------------------------------------
procedure TABSExprNodeArithmetic.GetCOSValue;
begin
  Value.AsDouble := Cos(TABSExprNode(Children[0]).GetDataValue.AsDouble);
end;// GetCOSValue


//------------------------------------------------------------------------------
// EXP operation
//------------------------------------------------------------------------------
procedure TABSExprNodeArithmetic.GetEXPValue;
begin
  Value.AsDouble := Exp(TABSExprNode(Children[0]).GetDataValue.AsDouble);
end;// GetEXPValue


//------------------------------------------------------------------------------
// FLOOR operation
//------------------------------------------------------------------------------
procedure TABSExprNodeArithmetic.GetFLOORValue;
begin
  Value.AsInteger := Floor(TABSExprNode(Children[0]).GetDataValue.AsDouble);
end;// GetFLOORValue


//------------------------------------------------------------------------------
// LOG operation
//------------------------------------------------------------------------------
procedure TABSExprNodeArithmetic.GetLOGValue;
begin
  Value.AsDouble := Ln(TABSExprNode(Children[0]).GetDataValue.AsDouble);
end;// GetLOGValue


//------------------------------------------------------------------------------
// POWER operation
//------------------------------------------------------------------------------
procedure TABSExprNodeArithmetic.GetPOWERValue;
begin
  Value.AsDouble := Power(TABSExprNode(Children[0]).GetDataValue.AsDouble,
                          TABSExprNode(Children[1]).GetDataValue.AsDouble);
end;// GetPOWERValue


//------------------------------------------------------------------------------
// RAND operation
//------------------------------------------------------------------------------
procedure TABSExprNodeArithmetic.GetRANDValue;
begin
  Value.AsInteger := Random(TABSExprNode(Children[0]).GetDataValue.AsInteger);
end;// GetRANDValue


//------------------------------------------------------------------------------
// ROUND operation
//------------------------------------------------------------------------------
procedure TABSExprNodeArithmetic.GetROUNDValue;
var
  RoundingType: tDecimalRoundingCtrl;
begin
  if (Children.Count = 1) then
    Value.AsInteger := Round(DecimalRoundDbl(TABSExprNode(Children[0]).GetDataValue.AsDouble,0))
  else
  if (Children.Count = 2) then
    Value.AsDouble := DecimalRoundDbl(TABSExprNode(Children[0]).GetDataValue.AsDouble,
                              TABSExprNode(Children[1]).GetDataValue.AsInteger)
  else
  if (Children.Count = 3) then
    begin
      case TReservedWord(TABSExprNode(Children[2]).GetDataValue.AsInteger) of
        rwHALFEVEN: RoundingType := drHalfEven;
        rwHALFPOS: RoundingType := drHalfPos;
        rwHALFNEG: RoundingType := drHalfNeg;
        rwHALFDOWN: RoundingType := drHalfDown;
        rwHALFUP: RoundingType := drHalfUp;
        rwRNDNEG: RoundingType := drRndNeg;
        rwRNDPOS: RoundingType := drRndPos;
        rwRNDDOWN: RoundingType := drRndDown;
        rwRNDUP: RoundingType := drRndUp;
      end;
      Value.AsDouble := DecimalRoundDbl(TABSExprNode(Children[0]).GetDataValue.AsDouble,
                          TABSExprNode(Children[1]).GetDataValue.AsInteger,
                          RoundingType);
    end;
end;// GetROUNDValue


//------------------------------------------------------------------------------
// SIGN operation
//------------------------------------------------------------------------------
procedure TABSExprNodeArithmetic.GetSIGNValue;
begin
  Value.AsInteger := Sign(TABSExprNode(Children[0]).GetDataValue.AsDouble);
end;// GetSIGNValue


//------------------------------------------------------------------------------
// SIN operation
//------------------------------------------------------------------------------
procedure TABSExprNodeArithmetic.GetSINValue;
begin
  Value.AsDouble := Sin(TABSExprNode(Children[0]).GetDataValue.AsDouble);
end;// GetSINValue


//------------------------------------------------------------------------------
// SQR operation
//------------------------------------------------------------------------------
procedure TABSExprNodeArithmetic.GetSQRValue;
begin
  Value.Assign(TABSExprNode(Children[0]).GetDataValue);
  Value.Mul(TABSExprNode(Children[0]).GetDataValue);
end;// GetSQRValue


//------------------------------------------------------------------------------
// SQRT operation
//------------------------------------------------------------------------------
procedure TABSExprNodeArithmetic.GetSQRTValue;
begin
  Value.AsDouble := Sqrt(TABSExprNode(Children[0]).GetDataValue.AsDouble);
end;// GetSQRTValue


//------------------------------------------------------------------------------
// Return Data Value
//------------------------------------------------------------------------------
function TABSExprNodeArithmetic.GetDataValue: TABSVariant;
begin
  case Operator of
   doSUB:       SubData;
   doADD:       AddData;
   doMUL:       MulData;
   doDIV:       DivData;
   doABS:       GetABSValue;
   doACOS:      GetACOSValue;
   doASIN:      GetASINValue;
   doATAN:      GetATANValue;
   doCEIL:      GetCEILValue;
   doCOS:       GetCOSValue;
   doEXP:       GetEXPValue;
   doFLOOR:     GetFLOORValue;
   doLOG:       GetLOGValue;
   doPOWER:     GetPOWERValue;
   doRAND:      GetRANDValue;
   doROUND:     GetROUNDValue;
   doSIGN:      GetSIGNValue;
   doSIN:       GetSINValue;
   doSQR:       GetSQRValue;
   doSQRT:      GetSQRTValue;
   else
     raise EABSException.Create(30121, ErrorGUnknownOperator,
                                [GetOperatorName(Operator)]);
  end;
  Result := Value;
end;//GetDataValue


//------------------------------------------------------------------------------
// get value data type
//------------------------------------------------------------------------------
function TABSExprNodeArithmetic.GetDataType: TABSAdvancedFieldType;
begin
  case Operator of
   doABS,
   doSQR:
     if Children.Count <> 0 then
       Result := TABSExprNode(Children[0]).getDataType;

   doCEIL,
   doFLOOR,
   doRAND,
   doSIGN:
     Result := aftInteger;

   doROUND:
     if Children.Count = 1 then
       Result := aftInteger
     else
       Result := aftDouble;

   doACOS,
   doASIN,
   doATAN,
   doCOS,
   doSIN,
   doEXP,
   doLOG,
   doSQRT,
   doPOWER:
     Result := aftDouble;

   else
     Result := inherited GetDataType;
  end;
end;// GetDataType


////////////////////////////////////////////////////////////////////////////////
//
// TABSExprNodeStringFunction
//
////////////////////////////////////////////////////////////////////////////////


//------------------------------------------------------------------------------
// InitStringValue and return IsNull
//------------------------------------------------------------------------------
function TABSExprNodeStringFunction.InitStringValue;
begin
  Value.Assign(TABSExprNode(Children[0]).GetDataValue);
  // Guarantee String or WideString type
  if not Value.IsStringDataType then
    Value.Cast(bftVarchar);
  Result := not Value.IsNull;
end;//InitStringValue


//------------------------------------------------------------------------------
// Concat
//------------------------------------------------------------------------------
procedure TABSExprNodeStringFunction.Concat;
var i: Integer;
begin
  InitStringValue;
  // Concatenation
  for i:=1 to Children.Count-1 do
   Value.Add(TABSExprNode(Children[i]).GetDataValue);
end;//Concat


//------------------------------------------------------------------------------
// Upper
//------------------------------------------------------------------------------
procedure TABSExprNodeStringFunction.Upper;
begin
  if (not InitStringValue) then
    Exit;
  // WideString ?
  if Value.IsWideStringDataType then
    Value.AsWideString := ABSStrUtils.WideUpperCase(Value.AsWideString)
  else
    Value.AsAnsiString := AnsiUpperCase(Value.AsAnsiString)
end;//Upper


//------------------------------------------------------------------------------
// Lower
//------------------------------------------------------------------------------
procedure TABSExprNodeStringFunction.Lower;
begin
  if (not InitStringValue) then
    Exit;
  // WideString ?
  if Value.IsWideStringDataType then
    Value.AsWideString := ABSStrUtils.WideLowerCase(Value.AsWideString)
  else
    Value.AsAnsiString := AnsiLowerCase(Value.AsAnsiString)
end;//Lower


//------------------------------------------------------------------------------
// Trim
//------------------------------------------------------------------------------
procedure TABSExprNodeStringFunction.Trim;
begin
  if (not InitStringValue) then
    Exit;
  // WideString ?
  if Value.IsWideStringDataType then
    Value.AsWideString := SysUtils.Trim(Value.AsWideString)
  else
    Value.AsAnsiString := SysUtils.Trim(Value.AsAnsiString)
end;//Trim


//------------------------------------------------------------------------------
// LTrim
//------------------------------------------------------------------------------
procedure TABSExprNodeStringFunction.LTrim;
begin
  if (not InitStringValue) then
    Exit;
  // WideString ?
  if Value.IsWideStringDataType then
    Value.AsWideString := SysUtils.TrimLeft(Value.AsWideString)
  else
    Value.AsAnsiString := SysUtils.TrimLeft(Value.AsAnsiString)
end;//LTrim


//------------------------------------------------------------------------------
// RTrim
//------------------------------------------------------------------------------
procedure TABSExprNodeStringFunction.RTrim;
begin
  if (not InitStringValue) then
    Exit;
  // WideString ?
  if Value.IsWideStringDataType then
    Value.AsWideString := SysUtils.TrimRight(Value.AsWideString)
  else
    Value.AsAnsiString := SysUtils.TrimRight(Value.AsAnsiString)
end;//RTrim


//------------------------------------------------------------------------------
// Length
//------------------------------------------------------------------------------
procedure TABSExprNodeStringFunction.Length;
var
  TmpVal: TABSVariant;
begin
  //Value.SetNull(bftSignedInt32);
  Value.AsInteger := 0;
  TmpVal := TABSVariant.Create;
  try
    TmpVal.Assign(TABSExprNode(Children[0]).GetDataValue);
    // Guarantee String or WideString type
    if (not TmpVal.IsStringDataType) then
      TmpVal.Cast(bftVarchar);
    // IsNull ?
    if (not TmpVal.IsNull) then
      case TmpVal.DataType of
        bftChar,
        bftVarchar:
          Value.AsInteger := System.Length(TmpVal.AsAnsiString);
        bftWideChar,
        bftWideVarchar:
          Value.AsInteger := System.Length(TmpVal.AsWideString);
      end;
  finally
    TmpVal.Free;
  end;
end;//Length


//------------------------------------------------------------------------------
// Pos
//------------------------------------------------------------------------------
procedure TABSExprNodeStringFunction.Pos;
var
  StrValue, SubstrValue: TABSVariant;
begin
  StrValue := TABSVariant.Create;
  SubstrValue := TABSVariant.Create;
  try
    StrValue.Assign(TABSExprNode(Children[0]).GetDataValue);
    SubstrValue.Assign(TABSExprNode(Children[1]).GetDataValue);
    // Guarantee String or WideString type
    if (not StrValue.IsStringDataType) then
      StrValue.Cast(bftVarchar);
    if (not SubStrValue.IsStringDataType) then
      SubStrValue.Cast(bftVarchar);
    case StrValue.DataType of
      bftChar,
      bftVarchar:
        case SubStrValue.DataType of
          bftChar,
          bftVarchar:
            Value.AsInteger := System.pos(StrValue.AsAnsiString, SubstrValue.AsAnsiString);
          bftWideChar,
          bftWideVarchar:
            Value.AsInteger := System.pos(StrValue.AsAnsiString, SubstrValue.AsWideString);
        end;
      bftWideChar,
      bftWideVarchar:
        case SubStrValue.DataType of
          bftChar,
          bftVarchar:
            Value.AsInteger := System.pos(StrValue.AsWideString, SubstrValue.AsAnsiString);
          bftWideChar,
          bftWideVarchar:
            Value.AsInteger := System.pos(StrValue.AsWideString, SubstrValue.AsWideString);
        end
    end;
  finally
    StrValue.Free;
    SubstrValue.Free;
  end;
end;//Pos


//------------------------------------------------------------------------------
// Substring
//------------------------------------------------------------------------------
procedure TABSExprNodeStringFunction.Substring;
var
  Start, Len: Integer;
begin
  // Start Position
  Start := TABSExprNode(Children[1]).GetDataValue.AsInteger;

  // length
  if (Children.Count >=3 ) then
   Len := TABSExprNode(Children[2]).GetDataValue.AsInteger
  else
   begin
     Length;
     len := Value.AsInteger;
   end;

  // String
  Value.Assign(TABSExprNode(Children[0]).GetDataValue);
  // String is null ?
  if (Value.IsNull) then
    Exit;
  // Guarantee String or WideString Type
  if (not Value.IsStringDataType) then
    Value.Cast(bftVarchar);

  // Get Substring
  if Value.IsWideStringDataType then
    Value.AsWideString := copy(Value.AsWideString, Start, Len)
  else
    Value.AsAnsiString := copy(Value.AsAnsiString, Start, Len);

end;//Substring


//------------------------------------------------------------------------------
// Return Data Value
//------------------------------------------------------------------------------
function TABSExprNodeStringFunction.GetDataValue: TABSVariant;
begin
  case Operator of
   doCONCAT:    Concat;
   doUPPER:     Upper;
   doLOWER:     Lower;
   doTRIM:      Trim;
   doLTRIM:     LTrim;
   doRTRIM:     RTrim;
   doSUBSTRING: Substring;
   doPOS:       Pos;
   doLENGTH:    Length;
   else
     raise EABSException.Create(30131, ErrorGUnknownOperator,
                                 [GetOperatorName(Operator)]);
  end;
  Result := Value;
end;//GetDataValue


//------------------------------------------------------------------------------
// return Type of Data
//------------------------------------------------------------------------------
function TABSExprNodeStringFunction.GetDataType: TABSAdvancedFieldType;
var
  BaseFieldType: TABSBaseFieldType;
begin
  case Operator of
    doPOS,
    doLENGTH:     Result := aftInteger;

    doUPPER,
    doLOWER,
    doTRIM,
    doLTRIM,
    doRTRIM,
    doSUBSTRING:  begin
                    Result := TABSExprNode(Children[0]).GetDataType;
                    if (not IsStringFieldType(Result)) then
                      begin
                        BaseFieldType := AdvancedFieldTypeToBaseFieldType(Result);
                        case BaseFieldType of
                          bftClob:      Result := aftString;
                          bftWideClob:  Result := aftWideString;
                        end;
                      end;
                  end;

    else          Result := inherited GetDataType;
  end;
end;//GetDataType


//------------------------------------------------------------------------------
// get Data Size
//------------------------------------------------------------------------------
function TABSExprNodeStringFunction.GetDataSize: Integer;
var i: Integer;
    DType, CommonType: TABSAdvancedFieldType;
    Cast: TABSExprNodeCast;
begin
  Result := 0;
  case Operator of
    doUPPER,
    doLOWER,
    doTRIM,
    doLTRIM,
    doRTRIM:
      Result :=  TABSExprNode(Children[0]).GetDataSize;
    doSUBSTRING:
      begin
        Result :=  TABSExprNode(Children[0]).GetDataSize;
        if (Result = 0) then // Blob or Clob ==> String
          begin
            CommonType := GetDataType;
            if IsStringFieldType(CommonType) then
              begin
                if (Children.Count = 3) then
                  Result := TABSExprNode(Children[2]).GetDataValue.AsInteger
                else
                  Result := DEFAULT_BLOB_TO_STRING_FIELD_SIZE;
              end;
          end;
      end;
    doCONCAT:
      begin
        CommonType := GetDataType;
        // Add "Cast to Common Type" Nodes
        for i:=0 to Children.Count-1 do
         begin
          DType := TABSExprNode(Children[i]).GetDataType;
          if (DType <> CommonType) and (not (TABSExprNode(Children[i]) is TABSExprNodeCast)) then
           begin
            Cast := TABSExprNodeCast.Create(TABSExprNode(Children[i]), CommonType);
            Children[i] := Cast;
           end;
         end;
        // Calculate DataSize
        for i:=0 to Children.Count-1 do
          Result := Result + TABSExprNode(Children[i]).GetDataSize;
      end;
  end;
end;//GetDataSize




////////////////////////////////////////////////////////////////////////////////
//
// TABSExprNodeSubquery
//
////////////////////////////////////////////////////////////////////////////////


//------------------------------------------------------------------------------
// CheckGetDataValue
//------------------------------------------------------------------------------
function TABSExprNodeSubquery.CheckGetDataValue: Boolean;
var
  ds: TABSDataset;
begin
  ds := TABSDataset(TABSSQLUnion(FSubquery).GetResultDataset);
  ds.First;
  if (not ds.Eof) then
    ds.GetFieldValue(FValue, 0, False)
  else
    FValue.SetNull;
  Result := True;
end;// CheckGetDataValue


//------------------------------------------------------------------------------
// CheckGetFieldValues
//------------------------------------------------------------------------------
function TABSExprNodeSubquery.CheckGetFieldValues: Boolean;
var
  ds: TABSDataset;
  i, OldLength: Integer;
begin
  ds := TABSDataset(TABSSQLUnion(FSubquery).GetResultDataset);
  OldLength := Length(FFieldValues);
  if (ds.Fields.Count > OldLength) then
    begin
       SetLength(FFieldValues, ds.Fields.Count);
       for i:=OldLength to ds.Fields.Count-1 do
         FFieldValues[i] := TABSVariant.Create;
    end
  else
    if (ds.Fields.Count < OldLength) then
      begin
        for i:=ds.Fields.Count to OldLength-1 do
          if (FFieldValues[i] <> nil) then
            FFieldValues[i].Free;
        SetLength(FFieldValues, ds.Fields.Count);
      end;

  ds.First;
  if (not ds.Eof) then
    begin
      for i:=0 to Length(FFieldValues)-1 do
        ds.GetFieldValue(FFieldValues[i], i, False)
    end
  else
    for i:=0 to Length(FFieldValues)-1 do
     FFieldValues[i].SetNull;
  Result := True;
end;// CheckGetFieldValues


//------------------------------------------------------------------------------
// Check EXISTS subquery results
//------------------------------------------------------------------------------
function TABSExprNodeSubquery.CheckExists: Boolean;
begin
  Result := (TABSSQLUnion(FSubquery).GetResultDataset.RecordCount > 0);
end;// CheckExists


//------------------------------------------------------------------------------
// Check IN subquery results
//------------------------------------------------------------------------------
function TABSExprNodeSubquery.CheckIsValueIn: Boolean;
var
  ds: TABSDataset;
begin
  ds := TABSDataset(TABSSQLUnion(FSubquery).GetResultDataset);
  if (FCorrelated) then
    Result := ds.Locate(ds.FieldDefs[0].Name, LValueToCheck.AsVariant, [])
  else
    begin
      if (FPriorResultInitalized and
          (LValueToCheck.Compare(FPriorValueToCheck, false, false) = cmprEqual)) then
        Result := FPriorResult
      else
        begin
          Result := ds.Locate(ds.FieldDefs[0].Name, LValueToCheck.AsVariant, []);
          FPriorResult := Result;
          FPriorResultInitalized := True;
          FPriorValueToCheck.Assign(LValueToCheck);
        end;
    end;
end;// CheckIsValueIn


//------------------------------------------------------------------------------
// Check > ALL subquery results
//------------------------------------------------------------------------------
function TABSExprNodeSubquery.CheckQuantifiedCompare: Boolean;
var
  ds: TABSDataset;
  op: TABSDataOperator;
begin
  ds := TABSDataset(TABSSQLUnion(FSubquery).GetResultDataset);
  ds.First;
  if (ds.Eof) then
    Result := False
  else
    begin
      // "const > field" => "field < const"
      op := ReverseComparisonOperator(FCompareOperator);
      if (FCompareQuantifier = qALL) then
        // ">" => "<="
        op := InvertComparisonOperator(op);
      Result := ds.Handle.IsAnyRecordMatchCondition(ds.FieldDefs[0].Name, op, LValueToCheck);
      if (FCompareQuantifier = qALL) then
        Result := not Result;
    end;
end;// CheckQuantifiedCompare


//------------------------------------------------------------------------------
// RunQueryAndCheckResults
//------------------------------------------------------------------------------
function TABSExprNodeSubquery.RunQueryAndCheckResults(CheckMethod: TCheckResultsMethod): Boolean;
var
  ReadOnly: Boolean;
  OldRecBuf: TABSRecordBuffer;
begin
  // cached non-correlated query?
  if ((not FCorrelated) and (FSubquery <> nil)) then
    Result := CheckMethod
  else
    begin
      LLexer.CurrentCommandNo := FCommandNo;
      LLexer.SetCurrentTokenNo(FTokenNo);
      FSubquery := TABSSQLUnion.Create(LLexer, LDataset);
      if (LCursor <> nil) then
        OldRecBuf := LCursor.CurrentRecordBuffer;
      try
        if (LCursor <> nil) then
          LCursor.CurrentRecordBuffer := LCursorRecordBuffer;
        TABSSQLUnion(FSubquery).ExecSQL(LDataset, True, True, ReadOnly, LAO, LCursor);
        if (FMustHaveOneField) then
          if (TABSSQLUnion(FSubquery).GetResultDataset.FieldCount <> 1) then
            raise EABSException.Create(20246, ErrorASubqueryMustHaveOneField,
                             [TABSSQLUnion(FSubquery).GetResultDataset.FieldCount]);
        if (FMustHaveOneRow) then
          with TABSSQLUnion(FSubquery).GetResultDataset do
            begin
             First;
             if (Eof) then
               raise EABSException.Create(20249, ErrorASubqueryMustHaveOneRowButEmpty);
             Next;
             if (not Eof) then
               raise EABSException.Create(20247, ErrorASubqueryMustHaveOneRow);
            end;
        Result := CheckMethod;
      finally
        if (LCursor <> nil) then
          LCursor.CurrentRecordBuffer := OldRecBuf;
        if (FCorrelated) then
          begin
            FSubquery.Free;
            FSubquery := nil;
          end;
      end;
    end;
end;// RunQueryAndCheckResults


//------------------------------------------------------------------------------
// Constructor
//------------------------------------------------------------------------------
constructor TABSExprNodeSubquery.Create(MustHaveOneField: Boolean; MustHaveOneRow: Boolean;
                   Dataset: TDataset; Lexer: TABSLexer);
begin
  inherited Create;
  FMustHaveOneField := MustHaveOneField;
  FMustHaveOneRow := MustHaveOneRow;
  FValue := TABSVariant.Create;
  LDataset := Dataset;
  LLexer := Lexer;
  FTokenNo := LLexer.GetCurrentTokenNo;
  FCommandNo := LLexer.CurrentCommandNo;
  FSubquery := TABSSQLUnion.Create(LLexer, Dataset);
  FSubquery.Free;
  FSubquery := nil;
  LCursorRecordBuffer := nil;
  LCursor := nil;
  FPriorValueToCheck := TABSVariant.Create;
  FPriorResultInitalized := false;
  FCorrelated := not CanBeAssigned(nil, nil, nil);
end;// Create


//------------------------------------------------------------------------------
// Destroy
//------------------------------------------------------------------------------
destructor TABSExprNodeSubquery.Destroy;
var
  i: Integer;
begin
  FValue.Free;
  FPriorValueToCheck.Free;
  if (FSubquery <> nil) then
    FSubquery.Free;
  for i:=0 to Length(FFieldValues)-1 do
    if (FFieldValues[i] <> nil) then
      FFieldValues[i].Free;
  inherited Destroy;
end;// Destroy


//------------------------------------------------------------------------------
// process assign cursor
//------------------------------------------------------------------------------
procedure TABSExprNodeSubquery.AssignCursor(Cursor: TABSCursor; ParentQueryAO: TABSAO; ParentCursor: TABSCursor);
begin
  LCursor := Cursor;
  AssignCursorBuffer(Cursor.CurrentRecordBuffer, False);
end;// AssignCursor


//------------------------------------------------------------------------------
// process assign New Cursor Buffer
//------------------------------------------------------------------------------
procedure TABSExprNodeSubquery.AssignCursorBuffer(Buffer: TABSRecordBuffer;
                                             ApplyToParentQueryFields: Boolean);
begin
  LCursorRecordBuffer := Buffer;
end;// AssignCursorBuffer


//------------------------------------------------------------------------------
// process assign AO
//------------------------------------------------------------------------------
procedure TABSExprNodeSubquery.AssignAO(AO, ParentQueryAO: TABSAO; ParentCursor: TABSCursor);
begin
  LAO := AO;
end;// AssignAO


//------------------------------------------------------------------------------
// CanBeAssigned
//------------------------------------------------------------------------------
function TABSExprNodeSubquery.CanBeAssigned(AO, ParentQueryAO: TABSAO;
                                            ParentCursor: TABSCursor; ForHavingClause: Boolean = False): Boolean;
var
  tmpAO: TABSAO;
  OldCurrentCommandNo, OldCurrentTokenNo: Integer;
begin
  OldCurrentCommandNo := LLexer.CurrentCommandNo;
  OldCurrentTokenNo := LLexer.GetCurrentTokenNo;
  try
    LLexer.CurrentCommandNo := FCommandNo;
    LLexer.SetCurrentTokenNo(FTokenNo);
    FSubquery := TABSSQLUnion.Create(LLexer, LDataset);
    try
      tmpAO := TABSSQLUnion(FSubquery).BuildAOTree(LDataset, True, AO, nil, True);
      try
        Result := TABSSQLUnion(FSubquery).AllConditionsApplied;
      finally
        tmpAO.Free;
      end;
    finally
      FSubquery.Free;
      FSubquery := nil;
    end;
  finally
    LLexer.CurrentCommandNo := OldCurrentCommandNo;
    LLexer.SetCurrentTokenNo(OldCurrentTokenNo);
  end;
end;// CanBeAssigned


//------------------------------------------------------------------------------
// Return Data Value
//------------------------------------------------------------------------------
function TABSExprNodeSubquery.GetDataValue: TABSVariant;
begin
  if RunQueryAndCheckResults(CheckGetDataValue) then
    Result := FValue
  else
    Result := nil;
end;// GetDataValue


//------------------------------------------------------------------------------
// calc subquery field values again
//------------------------------------------------------------------------------
procedure TABSExprNodeSubquery.ResetSubqueryFieldValues;
begin
  RunQueryAndCheckResults(CheckGetFieldValues);
end;// ResetSubqueryFieldValues


//------------------------------------------------------------------------------
// Return subquery field value
//------------------------------------------------------------------------------
function TABSExprNodeSubquery.GetSubqueryFieldValue(FieldNo: Integer): TABSVariant;
begin
  if (FieldNo < Length(FFieldValues)) then
    Result := FFieldValues[FieldNo]
  else
    Result := nil;
end;// GetSubqueryFieldValue


//------------------------------------------------------------------------------
// return Type of Data
//------------------------------------------------------------------------------
function TABSExprNodeSubquery.GetDataType: TABSAdvancedFieldType;
var
  tmpAO: TABSAO;
  FieldNo: Integer;
begin
  LLexer.CurrentCommandNo := FCommandNo;
  LLexer.SetCurrentTokenNo(FTokenNo);
  FSubquery := TABSSQLUnion.Create(LLexer, LDataset);
  try
    tmpAO := TABSSQLUnion(FSubquery).BuildAOTree(LDataset, True, nil, nil, True);
    try
      FieldNo := tmpAO.GetFieldNoByVisibleNumber(0);
      Result := tmpAO.GetFieldType(FieldNo);
    finally
      tmpAO.Free;
    end;
  finally
    FSubquery.Free;
    FSubquery := nil;
  end;
end;// GetDataType


//------------------------------------------------------------------------------
// return Data Size
//------------------------------------------------------------------------------
function TABSExprNodeSubquery.GetDataSize: Integer;
var
  tmpAO: TABSAO;
  FieldNo: Integer;
begin
  LLexer.CurrentCommandNo := FCommandNo;
  LLexer.SetCurrentTokenNo(FTokenNo);
  FSubquery := TABSSQLUnion.Create(LLexer, LDataset);
  try
    tmpAO := TABSSQLUnion(FSubquery).BuildAOTree(LDataset, True, nil, nil, True);
    try
      FieldNo := tmpAO.GetFieldNoByVisibleNumber(0);
      Result := tmpAO.GetFieldSize(FieldNo);
    finally
      tmpAO.Free;
    end;
  finally
    FSubquery.Free;
    FSubquery := nil;
  end;
end;// GetDataSize


//------------------------------------------------------------------------------
// EXISTS subquery
//------------------------------------------------------------------------------
function TABSExprNodeSubquery.Exists: Boolean;
begin
  Result := RunQueryAndCheckResults(CheckExists);
end;// Exists


//------------------------------------------------------------------------------
// IN subquery
//------------------------------------------------------------------------------
function TABSExprNodeSubquery.IsValueIn(Value: TABSVariant): Boolean;
begin
  LValueToCheck := Value;
  Result := RunQueryAndCheckResults(CheckIsValueIn);
end;// IsValueIn


//------------------------------------------------------------------------------
// > ALL subquery
//------------------------------------------------------------------------------
function TABSExprNodeSubquery.QuantifiedCompare(Operator: TABSDataOperator;
                       Quantifier: TABSQuantifier; Value: TABSVariant): Boolean;
begin
  FCompareOperator := Operator;
  FCompareQuantifier := Quantifier;
  LValueToCheck := Value;
  Result := RunQueryAndCheckResults(CheckQuantifiedCompare);
end;// QuantifiedCompare



//------------------------------------------------------------------------------
//FillFieldNames
//------------------------------------------------------------------------------
procedure FillFieldNames(List: TStringList; const Names: string);

  function ExtractFieldName(const Fields: string; var Pos: Integer): string;
  var
    I: Integer;
  begin
    I := Pos;
    while (I <= Length(Fields)) and (Fields[I] <> ';') do Inc(I);
    Result := Trim(Copy(Fields, Pos, I - Pos));
    if (I <= Length(Fields)) and (Fields[I] = ';') then Inc(I);
    Pos := I;
  end;//ExtractFieldName

var
  Pos: Integer;
begin
  List.Clear;
  Pos := 1;
  while Pos <= Length(Names) do
    List.Add(UpperCase(ExtractFieldName(Names, Pos)));
end;//FillFieldNames


//------------------------------------------------------------------------------
// "<" => ">", "=" => "="
//------------------------------------------------------------------------------
function ReverseComparisonOperator(CompOp: TABSDataOperator): TABSDataOperator;
begin
  case CompOp of
   doEQ: Result :=doEQ;
   doNE: Result :=doNE;
   doGT: Result :=doLT;
   doLT: Result :=doGT;
   doGE: Result :=doLE;
   doLE: Result :=doGE;
  else
    Result :=doUNDEFINED;
  end;
end;// ReverseComparisonOperator


//------------------------------------------------------------------------------
// "<" => ">=", "=" => "<>"
//------------------------------------------------------------------------------
function InvertComparisonOperator(CompOp: TABSDataOperator): TABSDataOperator;
begin
  case CompOp of
   doEQ: Result :=doNE;
   doNE: Result :=doEQ;
   doGT: Result :=doLE;
   doLT: Result :=doGE;
   doGE: Result :=doLT;
   doLE: Result :=doGT;
  else
    Result :=doUNDEFINED;
  end;
end;// InvertComparisonOperator


function IsNonConstantExpression(Value: TABSVariant): Boolean;
var
  expr: TABSExpression;
begin
  if (not IsStringFieldType(Value.DataType)) or
     (Value.IsNull) or (Value.AsAnsiString = '') then
    Result := False
  else
    begin
      expr := TABSExpression.Create;
      try
        if (expr.ParseForDefaultValueExpression(string(Value.AsWideString))) then
          begin
            if (expr.IsFieldsExpression) then
              Result := False
            else
              if (expr.GetValue <> nil) then
                Result := (expr.GetValue.AsWideString <> Value.AsWideString)
              else
                Result := False;
          end
        else
          Result := False
      finally
        expr.Free;
      end;
    end;
end;



end.
