unit ABSRelationalAlgebra;

interface

{$I ABSVer.inc}

uses Windows, Classes, Db, SysUtils, Math,

// AbsoluteDatabase units

 {$IFDEF DEBUG_LOG}
 ABSDebug,
 {$ENDIF}
 ABSConst,
 ABSConverts,
 ABSExcept,
 ABSBase,
 ABSTypes,
 ABSVariant
 ;

type

 TABSFieldLink = record
  FieldName:                string;   // table field name
  DisplayName:              string;   // result field name
  FieldType:                TABSAdvancedFieldType;
  FieldPrecision:           Integer;
  FieldSize:                Integer;
  BLOBCompressionAlgorithm: Byte;
  BLOBCompressionMode:      Byte;
  BLOBBlockSize:            Integer;
  AO:                       Pointer;  // was TABSAO type, CB4 bug fix
  Dataset:                  TDataset; // dataset
  IsHidden:                 Boolean;
  FieldNo:                  Integer;  // field number in AO or FieldNo
  IsExpression:             Boolean;  // Expression or field?
  IsAggregate:              Boolean;  // Expression is aggregate (contains agg. functions)?
  Expr:                     TObject;  // TABSExpression
 end;

 // fields (expressions) list in select
 TABSSelectListItem = record
  TableName:    string;   // 'table1.' | 't1'
  AllFields:    Boolean;  // 'table1.*' ?
  FieldName:    string;   // field1
  IsExpression: Boolean;  // field or expr?
  IsDuplicatedField: Boolean;  // duplicated field?
  ValueExpr:    TObject;  // TABSExpression
  Pseudonym:    string;   // field1 as f1
 end;

 // array of fields
 TABSFields = class
 public
   Items:       array of TABSSelectListItem; // fields
   ItemCount:   Integer;                     // length
   // creates
   constructor Create;
   // adds item to the end
   procedure Append(var Item: TABSSelectListItem);
 end;


 // base class for relational algebra operations
 TABSAO = class (TObject)
 public
  FIsRootAO:                  Boolean;
  FIsAOTable:                 Boolean;
  FIsAOGroupBy:               Boolean;
  FFilterExpr:                TObject; // TABSExpression
  FTopRowCount:               Int64;
  FFirstRowNo:                Int64;
  FHasSetResultFields:        Boolean;
  FResultTableName:           string; // for SELECT INTO optimization
 protected
  FResultInMemory:            Boolean; // for SELECT INTO optimization
  FResultImmediate:           Boolean; // for SELECT INTO optimization
  FResultDatabaseName:        string; // for SELECT INTO optimization
  FTableName:                 string;
  FTableAlias:                string;
  FIsMaterialized:            Boolean;
  FResultDataset:             TDataset; // result dataset
  FResultFieldsOrder:         TABSIntegerArray;
  FFieldCount:                Integer;
  FLeftAONull:                Boolean;
  FRightAONull:               Boolean;
  FDistinctApplied:           Boolean;
  FDistinctFields:            string;
  FDistinctFieldCount:        Integer;
  FDistinctFieldsMap:         array of Integer;
  FResultIndexFieldsList:             TStringList;
  FResultIndexAscDescFieldsList:      TStringList;
  FResultIndexCaseInsFieldsList:      TStringList;
  FExpressionsExists:         Boolean;
  FIsLocked:                  Boolean;
  FDisableTempFiles:          Boolean;
  FValue:                      TABSVariant;
 protected
  FFieldLinks:                array of TABSFieldLink;
 public
  FLeftAO,FRightAO:           TABSAO;
 protected
 procedure InternalCreate(
                      LeftAO:     TABSAO = nil;
                      RightAO:    TABSAO = nil;
                      TableName:  string = '';
                      TableAlias: string = ''
                      );
  // navigating
  procedure InternalFirst; virtual;
  procedure InternalNext; virtual;
  function InternalGetEof: Boolean; virtual;
  function InternalGetRecordCount: Integer; virtual;
  procedure First; virtual;
  procedure Next; virtual;
  function GetEof: Boolean; virtual;
  function GetRecordCount: Integer; virtual;
  // sets names to FieldLinks list and renames duplicate names
  procedure SetFieldNames; virtual;
  // materialization routines
  function CreateIndexForMaterialize(BeforeCreateTable: Boolean): string;
  procedure CreateTableForMaterialize(
                                            FieldList:  TStringList;
                                            AliasList:  TStringList
                                      );
  procedure FillTableForMaterialize;
  procedure ReplaceInIndexAliasesToFields(FieldList, AliasList,
                                          FResultIndexFieldsList: TStringList);
  procedure FinalizeMaterialize(
                                            FieldList:  TStringList;
                                            AliasList:  TStringList
                                );

  // materializes AO
  procedure DoMaterialize;

 public
  destructor Destroy; override;
  // gets all result records
  procedure Execute(IsRootAO: Boolean; ParentQueryAO: TABSAO; ParentCursor: TABSCursor); virtual;
  function LockTables: Boolean;
  function UnlockTables: Boolean;
  function OpenTables: Boolean;
  function CloseTables: Boolean;

  // sets filter
  procedure SetFilter(FilterExpr: TObject);
  // for SELECT INTO optimization
  procedure SetResultTable(InMemory, Immediate: Boolean; TableName: string; DatabaseName: string);
  // sets Top row count
  procedure SetTopRowCount(FirstRowNo, TopRowCount: Integer); virtual;
  // applies distinct
  procedure ApplyDistinct(DistinctFields: string);
  // sets projection for other TABSAO
  procedure SetResultFields(var FieldRefs: array of TABSSelectListItem;
                            bDistinct: Boolean); virtual;
  // mapping function - return number of found fields and found field No
  // also optionally unhides fields in AO
  function FieldExists(
                  FieldName, TableName: string;
                  Unhide:               Boolean;
                  FieldNumbers:         TABSIntegerArray = nil;
                  UnhideChildrenOnly:   Boolean = False;
                  ScanOnlyVisibleFields: Boolean = false
                      ): Integer; virtual;
  function FindFieldInFieldLinks(FieldName: string; var FieldNo: Integer): Boolean;
  function GetFieldName(FieldNo: Integer; ApplyOrderBy: Boolean = False): string;
  // return FieldName if field is not hidden and column name = field name or display name
  function GetFieldNameByColumnName(ColumnName: string): string;
  function GetFieldNameByColumnNo(ColumnNo: Integer): string;
  function GetFieldNameByVisibleNumber(VisibleFieldNo: Integer): string;
  function GetFieldNoByVisibleNumber(VisibleFieldNo: Integer): Integer;
  procedure GetFieldValue(
                        Value:      TABSVariant;
                        FieldNo:        Integer;
                        bCopy:          Boolean = False;
                        AccessToHidden: Boolean = False
                        );
  function GetFieldType(FieldNo: Integer): TABSAdvancedFieldType; overload;
  function GetFieldSize(FieldNo: Integer): Integer;
  function GetFieldPrecision(FieldNo: Integer): Integer;
  procedure CopyFieldValue(SrcFieldNo,DestFieldNo: Integer);
  function GetFieldDatsetAndFieldNo(var SrcFieldDatset: TDataset;
                                    var SrcFieldDatasetFieldNo: Integer): Boolean;
  // sets index
 private
  procedure InternalSetDistinct;
  procedure InternalSetIndex(ToClearFields: Boolean = True);
  procedure CreateResultIndexLists(ToClear: Boolean = True);
  procedure FreeResultIndexLists;
  procedure AddFieldLink(FieldLinkNo: Integer; SkipHiddenFields: Boolean = True);
 public
  procedure SetIndex(IndexFieldNames, DescFields, CaseInsensitiveFields: string);
    virtual;
  property IsMaterialized: Boolean read FIsMaterialized;
  property FieldCount: Integer read FFieldCount;
  property RecordCount: Integer read GetRecordCount;
  property ResultDataset: TDataset read FResultDataset;
  property Eof: Boolean read GetEof;
  property TableAlias: string read FTableAlias write FTableAlias;
 end;

 // table
 TABSAOTable = class (TABSAO)
 private
  FDatabase: TObject;
 public
  constructor Create(
                      TableName:  string;
                      TableAlias: string;
                      DB: TObject;
                      Table: TDataset
                      );
  destructor Destroy; override;
  procedure Execute(IsRootAO: Boolean; ParentQueryAO: TABSAO; ParentCursor: TABSCursor); override;
  // sets projection
  procedure SetResultFields(var FieldRefs: array of TABSSelectListItem;
                      bDistinct: Boolean); override;
  function TransferIndexToParentIfNeeded(var FieldNamesList, AscDescList, CaseSensitivityList: TStringList): Boolean;

  property IsMaterialized;
  property FieldCount;
  property RecordCount;
  property ResultDataset;
  property Eof;
 end;


 // joins and dekart
 TABSAOJoin = class (TABSAO)
 private
  FDekart:    Boolean;
  FOuterJoin: Boolean;
  FInnerJoin: Boolean;

  FJoinType:  TABSJoinType;
  FFields1:   TABSIntegerArray;
  FFields2:   TABSIntegerArray;
  FJoinCondition: TObject;

  // inner / outer joins
  FCompareResult: TABSCompareResult;
  FEqualStarted:      Boolean; // true if equal values in both AO
  FFirstTimeCalled:   Boolean; // true if Next called First time
  FEof:               Boolean; // Eof is set
  FRightBeginBookmark:TBookmark;
  FRightEndBookmark:  TBookmark;
  FBothNullsStarted:  Boolean;
  FLeftAOEmpty:       Boolean;
  FRightAOEmpty:      Boolean;
  FLinkCount:         Integer;
  FFieldsLink:        Boolean;

 protected
  // records are called Equal if all their join attributes are equal
  procedure CompareRecords; overload;
  procedure InternalFirst; override;
  procedure InternalNext; override;
  function InternalGetEof: Boolean; override;
  function InternalGetRecordCount: Integer; override;
 public
  constructor Create(
                      LeftChild:  TABSAO;
                      RightChild: TABSAO;
                      JoinType:   TABSJoinType;
                      IsNatural:  Boolean;
                      FieldList1: TABSFields; // join fields
                      FieldList2: TABSFields; // field1 = field2
                      JoinCondition: TObject
                      );
  destructor Destroy; override;
  procedure Execute(IsRootAO: Boolean; ParentQueryAO: TABSAO; ParentCursor: TABSCursor); override;

 public
  property IsMaterialized;
  property FieldCount;
  property RecordCount;
  property ResultDataset;
  property Eof;
  property OuterJoin: Boolean read FOuterJoin;
 end; // TABSAOJoin


 TABSAOUnion = class (TABSAO)
 private
  FEof:               Boolean; // Eof is set
  FUnionType:         TABSUnionType;
  FFields1:           TABSIntegerArray;
  FFields2:           TABSIntegerArray;
  FCompareResult:     TABSCompareResult;
  FFirstTimeCalled:   Boolean; // true if Next called First time
  FShowLeft:          Boolean; // if then leftAO records will be added otherwise right
 protected
  // records are called Equal if all their join attributes are equal
  procedure CompareRecords;
  procedure ShowLeftAO;
  procedure ShowRightAO;
  procedure InternalFirst; override;
  procedure InternalNext; override;
  function InternalGetEof: Boolean; override;
  function InternalGetRecordCount: Integer; override;
 public
  constructor Create(
                      LeftChild:  TABSAO;
                      RightChild: TABSAO;
                      UnionType:   TABSUnionType;
                      ParentQueryAO: TABSAO;
                      ParentCursor: TABSCursor;
                      IsCorresponding:  Boolean = False;
                      bDistinct: Boolean = True;
                      FieldList: TABSFields=nil // corresponding fields
                      );
  destructor Destroy; override;
 public
  property IsMaterialized;
  property FieldCount;
  property RecordCount;
  property ResultDataset;
  property Eof;
 end; // TABSAOUnion - union, intersect, except

 // table expression
 TABSAOTableExpr = class (TABSAO)
 private
  procedure ReplacePseudonymsInIndexFields;
  procedure AddHiddenIndexFields;

 protected
  procedure InternalFirst; override;
  procedure InternalNext; override;
  function InternalGetEof: Boolean; override;
  function InternalGetRecordCount: Integer; override;
 public
  constructor Create(
                     Child: TABSAO
                    );
  // sets Top row count
  procedure SetTopRowCount(FirstRowNo, TopRowCount: Integer); override;
  procedure Execute(IsRootAO: Boolean; ParentQueryAO: TABSAO; ParentCursor: TABSCursor); override;
 public
  property IsMaterialized;
  property FieldCount;
  property RecordCount;
  property ResultDataset;
  property Eof;
 end;

 // table expression
 TABSAOGroupBy = class (TABSAO)
 protected
  FTempDataset:       TDataset;
  FFirstTimeCalled:   Boolean; // true if Next called First time
  FAllFields:         Boolean;
  FFields:            TABSIntegerArray;
  FCompareResult:     TABSCompareResult;
  FGroupFinished:     Boolean;
  FEOF:               Boolean;
  GroupByFields: string;
 protected
  // records are called Equal if all their join attributes are equal
  procedure CompareRecords;
  procedure InternalFirst; override;
  procedure InternalNext; override;
  function InternalGetEof: Boolean; override;
  function InternalGetRecordCount: Integer; override;
  function IsCountAll: Boolean;
  procedure DoCountAll;
 public
  // sets projection
  procedure SetResultFields(var FieldRefs: array of TABSSelectListItem;
                      bDistinct: Boolean); override;
  constructor Create(
                     Child: TABSAO;
                     FieldList: TABSFields;
                     var FieldRefs: array of TABSSelectListItem
                    );
  destructor Destroy; override;
 public
  property IsMaterialized;
  property FieldCount;
  property RecordCount;
  property ResultDataset;
  property Eof;
 end;

 // table expression
 TABSAOSQLTopRowCount = class (TABSAO)
 protected
  procedure InternalFirst; override;
  procedure InternalNext; override;
  function InternalGetEof: Boolean; override;
  function InternalGetRecordCount: Integer; override;
 public
  constructor Create(
                     Child: TABSAO
                    );
  // sets Top row count
  procedure SetTopRowCount(FirstRowNo, TopRowCount: Integer); override;
 public
  property IsMaterialized;
  property FieldCount;
  property RecordCount;
  property ResultDataset;
  property Eof;
 end;


 procedure ConvertIndexFieldNamesToLists(
                FieldNames, DescFields, CaseInsFields:  string;
                FieldList, AscDescList, CaseInsList:    TStringList
                );
 procedure ConvertListsToIndexFieldNames(
                var FieldNames:     string;
                var DescNames:     string;
                var CaseInsNames:  string;
                FieldList, AscDescList, CaseInsList:    TStringList
                );

implementation

uses ABSMain,ABSExpressions;


////////////////////////////////////////////////////////////////////////////////
//
// TABSFields
//
////////////////////////////////////////////////////////////////////////////////


//------------------------------------------------------------------------------
// creates
//------------------------------------------------------------------------------
constructor TABSFields.Create;
begin
 ItemCount := 0;
end;// Create


//------------------------------------------------------------------------------
// add item to the end
//------------------------------------------------------------------------------
procedure TABSFields.Append(var Item: TABSSelectListItem);
begin
  Inc(ItemCount);
  SetLength(Items, ItemCount);
  Items[ItemCount-1] := Item;
end;// Append


////////////////////////////////////////////////////////////////////////////////
//
// TABSAO
//
////////////////////////////////////////////////////////////////////////////////


//------------------------------------------------------------------------------
// create
//------------------------------------------------------------------------------
procedure TABSAO.InternalCreate(
                      LeftAO:     TABSAO = nil;
                      RightAO:    TABSAO = nil;
                      TableName:  string = '';
                      TableAlias: string = ''
                      );
begin
  FResultDataset := TABSTable.Create(nil);
  if (LeftAO <> nil) then
    if (LeftAO.ResultDataset <> nil) then
      if (TABSTable(LeftAO.ResultDataset).SessionName <> '') then
        TABSTable(FResultDataset).SessionName := TABSTable(LeftAO.ResultDataset).SessionName;
  if (RightAO <> nil) then
    if (RightAO.ResultDataset <> nil) then
      if (TABSTable(RightAO.ResultDataset).SessionName <> '') then
        TABSTable(FResultDataset).SessionName := TABSTable(RightAO.ResultDataset).SessionName;
  FIsRootAO := False;
  FIsAOTable := False;
  FIsAOGroupBy := False;
  FHasSetResultFields := False;
  FTableName := TableName;
  FTableAlias := TableAlias;
  FLeftAO := LeftAO;
  FRightAO := RightAO;
  FResultFieldsOrder := TABSIntegerArray.Create(0,1,100);
  FIsMaterialized := True;
  FFieldCount := 0;
  FLeftAONull := False;
  FRightAONull := False;
  FDistinctApplied := False;
  FDistinctFields := '';
  FFilterExpr := nil;
  FTopRowCount := -1;
  FFirstRowNo := -1;
  FExpressionsExists := False;
  FResultIndexFieldsList := nil;
  FResultIndexAscDescFieldsList := nil;
  FResultIndexCaseInsFieldsList := nil;
  FResultTableName := '';
  FResultDatabaseName := '';
  FResultInMemory := False;
  FResultImmediate := False;
  FIsLocked := False;
  if (FLeftAO <> nil) then
    FDisableTempFiles := FLeftAO.FDisableTempFiles
  else
    FDisableTempFiles := False;
  if (FRightAO <> nil) then
    FDisableTempFiles := FDisableTempFiles or FRightAO.FDisableTempFiles;
  FValue := TABSVariant.Create;
end; // InternalCreate


//------------------------------------------------------------------------------
// go to first record
//------------------------------------------------------------------------------
procedure TABSAO.InternalFirst;
begin
;
end; // First


//------------------------------------------------------------------------------
// go to next record
//------------------------------------------------------------------------------
procedure TABSAO.InternalNext;
begin
;
end; // Next


//------------------------------------------------------------------------------
// return true if cursor points to the last record
//------------------------------------------------------------------------------
function TABSAO.InternalGetEof: Boolean;
begin
 Result := False;
end; // InternalGetEof


//------------------------------------------------------------------------------
// return number of records
//------------------------------------------------------------------------------
function TABSAO.InternalGetRecordCount: Integer;
begin
 Result := 0;
 if (FIsMaterialized) then
   Result := FResultDataset.RecordCount;
end; // InternalGetRecordCount


//------------------------------------------------------------------------------
// go to first record
//------------------------------------------------------------------------------
procedure TABSAO.First;
begin
  if (FIsMaterialized) then
    FResultDataset.First
  else
    InternalFirst;
end; // First


//------------------------------------------------------------------------------
// go to next record
//------------------------------------------------------------------------------
procedure TABSAO.Next;
begin
  if (FIsMaterialized) then
    FResultDataset.Next
  else
    InternalNext;
end; // Next


//------------------------------------------------------------------------------
// return true if cursor points to the last record
//------------------------------------------------------------------------------
function TABSAO.GetEof: Boolean;
begin
  if (FIsMaterialized) then
    Result := FResultDataset.Eof
  else
  Result := InternalGetEof;
end; // GetEof


//------------------------------------------------------------------------------
// return number of records
//------------------------------------------------------------------------------
function TABSAO.GetRecordCount: Integer;
begin
  if (FIsMaterialized) then
    Result := FResultDataset.RecordCount
  else
    Result := InternalGetRecordCount;
end; // GetRecordCount


//------------------------------------------------------------------------------
// sets names to FieldLinks list and renames duplicate names
//------------------------------------------------------------------------------
procedure TABSAO.SetFieldNames;
var i,j,k:    Integer;
    name,s:   string;
    bOk:      Boolean;
begin
  for i := 0 to FFieldCount-1 do
    begin
      FFieldLinks[i].FieldName := GetFieldName(i);
      // renaming fields without name (calculated, expressions...)
      if (FFieldLinks[i].FieldName = '') then
        FFieldLinks[i].FieldName := GetTemporaryName(ABSExpressionFieldName);
    end;
  // try to remove duplicates by renaming hidden fields only
  for i := 0 to FFieldCount-1 do
    begin
      k := 1;
      if not (FFieldLinks[i].IsHidden) then Continue;
      name := FFieldLinks[i].FieldName;
      repeat
        s := AnsiUpperCase(name);
        bOk := true;
        for j := 0 to i-1 do
          if (s = AnsiUpperCase(FFieldLinks[j].FieldName)) then
            begin
              bOk := false;
              break;
            end;
        if (bOk) then
          Break;
        if (k = 1)  then
          name := name +'_' + IntToStr(k)
        else
          name := name + IntToStr(k);
        inc(k);
      until bOk;
      FFieldLinks[i].FieldName := name;
  end;
  // remove all duplicates
  for i := 1 to FFieldCount-1 do
    begin
      k := 1;
      name := FFieldLinks[i].FieldName;
      repeat
        s := AnsiUpperCase(name);
        bOk := true;
        for j := 0 to i-1 do
          if (s = AnsiUpperCase(FFieldLinks[j].FieldName)) then
            begin
              bOk := false;
              break;
            end;
        if (bOk) then
          Break;
        if (k = 1)  then
          name := name +'_' + IntToStr(k)
        else
          name := name + IntToStr(k);
        inc(k);
      until bOk;
      FFieldLinks[i].FieldName := name;
  end;
end;


//------------------------------------------------------------------------------
// create table
//------------------------------------------------------------------------------
function TABSAO.CreateIndexForMaterialize(BeforeCreateTable: Boolean): string;
var IndexDef: TIndexDef;
    FieldNames, DescNames, CaseInsNames: string;
begin
  ConvertListsToIndexFieldNames(FieldNames,DescNames,CaseInsNames,
    FResultIndexFieldsList,FResultIndexAscDescFieldsList,
    FResultIndexCaseInsFieldsList);
  Result := GetTemporaryName(ABSTemporaryIndexName);
  if (BeforeCreateTable) then
   begin
    IndexDef := TABSTable(FResultDataset).IndexDefs.AddIndexDef;
    IndexDef.Name := Result;
    IndexDef.Fields := FieldNames;
    IndexDef.DescFields := DescNames;
    IndexDef.CaseInsFields := CaseInsNames;
   end
  else
   begin
    TABSTable(FResultDataset).AddIndex(Result,FieldNames,[],DescNames,CaseInsNames);
   end;
end; // CreateIndexForMaterialize


//------------------------------------------------------------------------------
// create table
//------------------------------------------------------------------------------
procedure TABSAO.CreateTableForMaterialize(
                                            FieldList:  TStringList;
                                            AliasList:  TStringList
                                          );
var i,j:        Integer;
    FieldDef:   TABSAdvFieldDef;
    (*DuplicateCount: Integer;*)
    Name: string;

procedure CreateIndexForDistinct;
var
  IndexDef: TIndexDef;
begin
  IndexDef := TABSTable(FResultDataset).IndexDefs.AddIndexDef;
  IndexDef.Name := GetTemporaryName(ABSTemporaryIndexName);
  IndexDef.Fields := FDistinctFields;
  IndexDef.DescFields := '';
  IndexDef.CaseInsFields := '';
  IndexDef.Options := [ixUnique];
end;

begin
   // prepare field defs
   TABSTable(FResultDataset).FieldDefs.Clear;
   TABSTable(FResultDataset).IndexDefs.Clear;
   TABSTable(FResultDataset).AdvFieldDefs.Clear;
   TABSTable(FResultDataset).AdvIndexDefs.Clear;

   // result table is a table with applied projection (output fields list)
   if (FResultFieldsOrder.ItemCount <= 0) then
    begin
     // no projection
     // not result table
     for i := 0 to FFieldCount-1 do
      begin
        // field names will be 'Field'+n
        if (FFieldLinks[i].IsHidden) then continue;

        (*DuplicateCount := 0;*)
        Name := FFieldLinks[i].FieldName;
        (*// 5.05 rename duplicate fields
        if TABSTFFieldLinks[i].FieldNameable(FResultDataset).AdvFieldDefs.Find(FFieldLinks[i].FieldName) <> nil then
        repeat
          Name := FFieldLinks[i].FieldName + '_' + IntToStr(DuplicateCount + 1);
          Inc(DuplicateCount);
        until TABSTable(FResultDataset).AdvFieldDefs.Find(Name) = nil;*)

        FieldDef := TABSTable(FResultDataset).AdvFieldDefs.AddFieldDef;
        FieldDef.Name := Name;

        FieldList.Add(Name);
        AliasList.Add('');

        if (IsAutoincFieldType(FFieldLinks[i].FieldType)) then
         FieldDef.DataType := BaseFieldTypeToAdvancedFieldType(AdvancedFieldTypeToBaseFieldType(FFieldLinks[i].FieldType))
        else
         FieldDef.DataType := FFieldLinks[i].FieldType;
        FieldDef.Size := FFieldLinks[i].FieldSize;
        FieldDef.BLOBCompressionAlgorithm :=
          TCompressionAlgorithm(FFieldLinks[i].BLOBCompressionAlgorithm);
        FieldDef.BLOBCompressionMode := FFieldLinks[i].BLOBCompressionMode;
        FieldDef.BLOBBlockSize := FFieldLinks[i].BLOBBlockSize;
      end; // not result table
    end
   else
    begin
     // projection is set

     // insert into result table is unavailable due to possible hidden fields
     if (not FIsRootAO) or (not FResultImmediate) then
       begin
         FResultTableName := '';
         FResultDatabaseName := '';
       end;

     // perpare projection
     for i := 0 to FResultFieldsOrder.ItemCount-1 do
      begin
       j := FResultFieldsOrder.Items[i];
       if (FFieldLinks[j].IsHidden) then
        raise EABSException.Create(10312,ErrorLCannotAccessHiddenField,[FTableName,
                FFieldLinks[j].FieldName,FFieldLinks[j].DisplayName,j]);
       if (FFieldLinks[j].DisplayName <> '') then
        begin
         FieldList.Add(FFieldLinks[j].FieldName);
         AliasList.Add(FFieldLinks[j].DisplayName);
        end;
      end;

     // prepare visible fields
     for i := 0 to FFieldCount-1 do
      begin
       if (FFieldLinks[i].IsHidden) then continue;


        (*DuplicateCount := 0;*)
        Name := FFieldLinks[i].FieldName;
        (*// 5.05 rename duplicate fields
        if TABSTable(FResultDataset).AdvFieldDefs.Find(FFieldLinks[i].FieldName) <> nil then
        repeat
          Name := FFieldLinks[i].FieldName + '_' + IntToStr(DuplicateCount + 1);
          Inc(DuplicateCount);
        until TABSTable(FResultDataset).AdvFieldDefs.Find(Name) = nil;*)

        FieldDef := TABSTable(FResultDataset).AdvFieldDefs.AddFieldDef;
        FieldDef.Name := Name;

        (*if FResultFieldsOrder.IndexOf(i) > -1 then
          FieldList[FResultFieldsOrder.IndexOf(i)] := Name;*)

        //FieldDef := TABSTable(FResultDataset).AdvFieldDefs.AddFieldDef;
        //FieldDef.Name := FFieldLinks[i].FieldName;
        
        if (FFieldLinks[i].FieldType = aftAutoInc) then
         FieldDef.DataType := aftInteger
        else
         FieldDef.DataType := FFieldLinks[i].FieldType;
        FieldDef.Size := FFieldLinks[i].FieldSize;
        FieldDef.BLOBCompressionAlgorithm :=
          TCompressionAlgorithm(FFieldLinks[i].BLOBCompressionAlgorithm);
        FieldDef.BLOBCompressionMode := FFieldLinks[i].BLOBCompressionMode;
        FieldDef.BLOBBlockSize := FFieldLinks[i].BLOBBlockSize;
      end;
    end; // result table
   if (FTopRowCount > -1) then
    if (FResultIndexFieldsList <> nil) then
     TABSTable(FResultDataset).IndexName := CreateIndexForMaterialize(True);
   // create table
   if (FResultTableName <> '') then
    begin
     TABSTable(FResultDataset).DatabaseName := FResultDatabaseName;
     TABSTable(FResultDataset).TableName := FResultTableName;
     TABSTable(FResultDataset).InMemory := FResultInMemory;
     TABSTable(FResultDataset).Temporary := False;
    end // result table
   else
    begin
     TABSTable(FResultDataset).InMemory := False;
     TABSTable(FResultDataset).Temporary := True;
     TABSTable(FResultDataset).DisableTempFiles := FDisableTempFiles;
     repeat
       TABSTable(FResultDataset).TableName := GetTemporaryName(ABSTemporaryTableName);
     until (not (TABSTable(FResultDataset).Exists));
    end; // temporary table

   if (FDistinctApplied) then
     CreateIndexForDistinct;

   TABSTable(FResultDataset).CreateTable;
   TABSTable(FResultDataset).Open;

end; // CreateTableForMaterialize


//------------------------------------------------------------------------------
// fill table
//------------------------------------------------------------------------------
{DEFINE PROFILE}
procedure TABSAO.FillTableForMaterialize;
var i,j,k,d:   Integer;
    values:    array of TABSVariant;
    bOK:       Boolean;

{$IFDEF PROFILE}
  StartTime: Cardinal;
{$ENDIF}
begin
 if (FDistinctApplied) then
  begin
   SetLength(values,FDistinctFieldCount);
   for i := 0 to FDistinctFieldCount-1 do
     values[i] := TABSVariant.Create;
  end
 else
  FDistinctFieldCount := 0;

 try
   // filling table with data
   First;
   k := 0;
   while Not Eof do
    begin
  {$IFDEF PROFILE}
  StartTime := GetTickCount;
  {$ENDIF}

     inc(k);
     // check TOP n?
      // TOP n?
      if (FTopRowCount > -1) then
      begin
       if (TABSDataset(FResultDataset).RecordCount >= FTopRowCount) then
        break;
      end;
      // TOP first_row
      if (FFirstRowNo > -1)  then
      begin
       if (k < FFirstRowNo) then
        begin
         Next;
         continue;
        end;
      end;

     if (FFilterExpr <> nil) then
      begin
       // check filter
       if (not TABSExpression(FFilterExpr).GetResult) then
        begin
         Next;
         continue;
        end;
      end;

     // check distinct
     if (FDistinctApplied) then
      begin
       bOK := True;
       for d := 0 to FDistinctFieldCount-1 do
        begin
         // read current record
         GetFieldValue(FValue,FDistinctFieldsMap[d],false,true);
         if (not TABSTable(FResultDataset).Bof) then
          if (FValue.Compare(values[d],False,False,False) <> cmprEqual) then
           bOK := False;
         values[d].Assign(FValue,True);
        end;//for

       if ((bOK) and (not TABSTable(FResultDataset).Bof)) then
        begin
         // skip this record
         Next;
         Continue;
        end;
      end;

     TABSTable(FResultDataset).Insert;

       // not result table
       j := 0;
       for i := 0 to FFieldCount-1 do
        begin
         if (FFieldLinks[i].IsHidden) then continue;
         CopyFieldValue(i,j);
         inc(j);
        end; // not result table
     // insert record
     if (not FDistinctApplied) then
       TABSTable(FResultDataset).Post
     else
       // check distinct
       if (TABSTable(FResultDataset).Handle.CheckConstraints(True)) then
         TABSTable(FResultDataset).Post
       else
         TABSTable(FResultDataset).Cancel;
     // go to next record

     Next;

   {$IFDEF PROFILE}
   StartTime := GetTickCount - StartTime;
   if StartTime > 0 then OutputDebugString(PChar(IntToStr(k) + ': ' + IntToStr(StartTime)));
   //OutputDebugString(PAnsiChar(IntToStr(k)));
   {$ENDIF}

    end; // enf of inserting records loop
   if (FTopRowCount > -1) then
    FTopRowCount := -1;
 finally
   if (FDistinctApplied) then
     begin
       for i := 0 to FDistinctFieldCount-1 do
         values[i].Free;
       values := nil;
     end;
 end;

end; // FillTableForMaterialize


//------------------------------------------------------------------------------
// ReplaceInIndexAliasesToFields
//------------------------------------------------------------------------------
procedure TABSAO.ReplaceInIndexAliasesToFields(FieldList, AliasList,
                                          FResultIndexFieldsList: TStringList);
var
  i,j: Integer;
  IsField: Boolean;
begin
  for i := 0 to AliasList.Count-1 do
  begin
     // first check if alias is a real field name
     IsField := False;
     for j := 0 to FieldList.Count-1 do
       if (AnsiUpperCase(AliasList[i]) = AnsiUpperCase(FieldList[j])) then
       begin
         IsField := True;
         Break;
       end;
     if IsField then Continue;
     //
     for j := 0 to FResultIndexFieldsList.Count-1 do
       if (AnsiUpperCase(AliasList[i]) = AnsiUpperCase(FResultIndexFieldsList[j])) then
         FResultIndexFieldsList[j] := FieldList[i];
  end;
end;// ReplaceInIndexAliasesToFields


//------------------------------------------------------------------------------
// finalize materialize
//------------------------------------------------------------------------------
procedure TABSAO.FinalizeMaterialize(
                                            FieldList:  TStringList;
                                            AliasList:  TStringList
                                    );
var i,j: Integer;
begin
 if (FResultIndexFieldsList <> nil) then
  if (not ((FTopRowCount > -1) or (FDistinctApplied))) then
   begin
       ReplaceInIndexAliasesToFields(FieldList, AliasList, FResultIndexFieldsList);
     TABSTable(FResultDataset).IndexName := CreateIndexForMaterialize(False);
   end;
 TABSTable(FResultDataset).ApplyProjection(FieldList,AliasList);
 // move field links to the result dataset
 if (FResultFieldsOrder.ItemCount <= 0) then
  begin
   j := 0;
   // not result table
   for i := 0 to FFieldCount-1 do
    begin
     // field names will be 'Field'+n
     if (FFieldLinks[i].IsHidden) then continue;
     FFieldLinks[i].AO := nil;
     if (FFieldLinks[i].IsExpression) then
      if (FFieldLinks[i].Expr <> nil) then
        TABSExpression(FFieldLinks[i].Expr).Free;
     FFieldLinks[i].IsExpression := False;
     FFieldLinks[i].IsAggregate := False;
     FFieldLinks[i].Expr := nil;
     FFieldLinks[i].Dataset := FResultDataset;
     FFieldLinks[i].FieldNo := j;
     inc(j);
    end; // not result table
  end
 else
  begin
   //--- projection is set
   if (FIsRootAO) then
     for i := 0 to FResultFieldsOrder.ItemCount-1 do
      begin
       j := FResultFieldsOrder.Items[i];
       if (FFieldLinks[j].IsHidden) then
        raise EABSException.Create(10314,ErrorLCannotAccessHiddenField,
              [FTableName,FFieldLinks[j].FieldName,FFieldLinks[j].DisplayName,j]);

       FFieldLinks[j].AO := nil;
       if (FFieldLinks[j].IsExpression) then
        if (FFieldLinks[j].Expr <> nil) then
         TABSExpression(FFieldLinks[j].Expr).Free;
       FFieldLinks[j].IsExpression := False;
       FFieldLinks[j].IsAggregate := False;
       FFieldLinks[j].Expr := nil;
       FFieldLinks[j].Dataset := FResultDataset;
       FFieldLinks[j].FieldNo := i;
      end
   else
     begin
       j := 0;
       for i := 0 to FFieldCount-1 do
         if (not FFieldLinks[i].IsHidden) then
           begin
             FFieldLinks[i].AO := nil;
             if (FFieldLinks[i].IsExpression) then
              if (FFieldLinks[i].Expr <> nil) then
               TABSExpression(FFieldLinks[i].Expr).Free;
             FFieldLinks[i].IsExpression := False;
             FFieldLinks[i].IsAggregate := False;
             FFieldLinks[i].Expr := nil;
             FFieldLinks[i].Dataset := FResultDataset;
             FFieldLinks[i].FieldNo := j;
             Inc(j);
           end;
     end;
  end; // result table

 FIsMaterialized := True;
 //---------------------- destroy child operations -----------------------------
 if (FIsAOGroupBy) then
   if (TABSAOGroupBy(Self).FTempDataset <> nil) then
      begin
        TABSAOGroupBy(Self).FTempDataset.Free;
        TABSAOGroupBy(Self).FTempDataset := nil;
      end;
 if (FLeftAO <> nil) then
  FLeftAO.Free;
 if (FRightAO <> nil) then
  FRightAO.Free;
 FRightAO := nil;
 FLeftAO := nil;
end; // FinalizeMaterialize


//------------------------------------------------------------------------------
// materializes AO
//------------------------------------------------------------------------------
procedure TABSAO.DoMaterialize;
var FieldList,AliasList: TStringList;
begin
 if (not FIsMaterialized) then
  begin
   FieldList := TStringList.Create;
   AliasList := TStringList.Create;
   try
     CreateTableForMaterialize(FieldList,AliasList);
     FillTableForMaterialize;
     FinalizeMaterialize(FieldList,AliasList);
   finally
     FieldList.Free;
     AliasList.Free;
   end;
  end;
end; // DoMaterialize


//------------------------------------------------------------------------------
// destroy
//------------------------------------------------------------------------------
destructor TABSAO.Destroy;
var
  i: integer;
begin
  if (FIsAOTable) then
    if (FIsLocked) then
      if (ResultDataset.Active) then
        TABSTable(ResultDataset).Handle.UnlockTable(ltS, True);

  for i := 0 to Length(FFieldLinks)-1 do
    if (FFieldLinks[i].IsExpression) then
      if (FFieldLinks[i].Expr <> nil) then
        TABSExpression(FFieldLinks[i].Expr).Free;

  FFieldLinks := nil;
  if (FResultDataset <> nil) then
    begin
      FResultDataset.Close;
      try
        if (TABSTable(FResultDataset).Temporary) then
          TABSTable(FResultDataset).DeleteTable;
      except
      end;
      FResultDataset.Free;
    end;
  if (FResultFieldsOrder <> nil) then
    FResultFieldsOrder.Free;
  if (FLeftAO <> nil) then
    FLeftAO.Free;
  if (FRightAO <> nil) then
    FRightAO.Free;
  if (FFilterExpr <> nil) then
    TABSExpression(FFilterExpr).Free;
  FreeResultIndexLists;
  FValue.Free;
  inherited Destroy;
end; // Destroy


//------------------------------------------------------------------------------
// getting all result records
//------------------------------------------------------------------------------
procedure TABSAO.Execute(IsRootAO: Boolean; ParentQueryAO: TABSAO; ParentCursor: TABSCursor);
var
  p: Pointer;
  IsCreated: Boolean;
begin
  FIsRootAO := IsRootAO;

  // filter
  if (not FIsAOTable) then
   if (FFilterExpr <> nil) then
     TABSExpression(FFilterExpr).AssignAO(Self, ParentQueryAO, ParentCursor);

  if (FLeftAO <> nil) then
    FLeftAO.Execute(False, ParentQueryAO, ParentCursor);
  if (FRightAO <> nil) then
    FRightAO.Execute(False, ParentQueryAO, ParentCursor);

  // optimize join

  if ((FLeftAO <> nil) and (FRightAO <> nil)) then
    if (Self is TABSAOJoin) then
      if (TABSAOJoin(Self).FJoinType in [ajtInner,ajtFullOuter]) then
      // bug-fix: 4.84 - speed up join with filtered table when LeftAO has much more records than RightAO
//        if (TABSTable(FLeftAO.ResultDataset).Handle.SQLFilterExpression = nil) and
//           (TABSTable(FRightAO.ResultDataset).Handle.SQLFilterExpression = nil) then
          if (FRightAO.ResultDataset.RecordCount < FLeftAO.ResultDataset.RecordCount) then
            begin
              p := FLeftAO;
              FLeftAO := FRightAO;
              FRightAO := p;
              p := TABSAOJoin(Self).FFields1;
              TABSAOJoin(Self).FFields1 := TABSAOJoin(Self).FFields2;
              TABSAOJoin(Self).FFields2 := p;
            end;

  if (FIsAOGroupBy) then
   if (not TABSAOGroupBy(Self).FAllFields) then
     if (TABSAOGroupBy(Self).FTempDataset = nil) and (FLeftAO <> nil) then
      begin
       TABSAOGroupBy(Self).FTempDataset := TABSTable.Create(nil);
       TABSTable(TABSAOGroupBy(Self).FTempDataset).DatabaseName :=
        TABSTable(FLeftAO.FResultDataset).DatabaseName;
       TABSTable(TABSAOGroupBy(Self).FTempDataset).SessionName :=
        TABSTable(FLeftAO.FResultDataset).SessionName;
       TABSTable(TABSAOGroupBy(Self).FTempDataset).TableName :=
        TABSTable(FLeftAO.FResultDataset).TableName;
       TABSTable(TABSAOGroupBy(Self).FTempDataset).InMemory :=
        TABSTable(FLeftAO.FResultDataset).InMemory;
       TABSTable(TABSAOGroupBy(Self).FTempDataset).Temporary :=
        TABSTable(FLeftAO.FResultDataset).Temporary;
       TABSTable(TABSAOGroupBy(Self).FTempDataset).ReadOnly := True;

       try
        TABSAOGroupBy(Self).FTempDataset.Open;
       except
        raise EABSException.Create(10304,ErrorLCannotOpenTable,
          [TABSTable(TABSAOGroupBy(Self).FTempDataset).TableName,
           Word(TABSTable(TABSAOGroupBy(Self).FTempDataset).InMemory)]);
       end;

       if (TABSTable(FLeftAO.FResultDataset).IsDistinctApplied) then
         TABSTable(TABSAOGroupBy(Self).FTempDataset).ApplyDistinct(TABSTable(FLeftAO.FResultDataset))
       else
         TABSTable(TABSAOGroupBy(Self).FTempDataset).IndexName :=
           TABSTable(FLeftAo.FResultDataset).IndexName;
      end; // FIsAOGroupBy

 if (FTopRowCount > 0) and (FResultIndexFieldsList <> nil) and (FLeftAO <> nil) then
  begin
   TABSTable(FLeftAo.FResultDataset).IndexName :=
     TABSTable(FLeftAo.FResultDataset).FindOrCreateIndex(
      FResultIndexFieldsList,FResultIndexAscDescFieldsList,FResultIndexCaseInsFieldsList,IsCreated)
  end;

 if ((FLeftAO <> nil) or (FRightAO <> nil)) then
  DoMaterialize;
 if (FResultIndexFieldsList <> nil) then
  InternalSetIndex(True);
end;// Execute


//------------------------------------------------------------------------------
// LockTables
//------------------------------------------------------------------------------
function TABSAO.LockTables: Boolean;
begin
  if (FIsAOTable) then
    begin
      if (ResultDataset.Active) then
        Result := TABSTable(ResultDataset).Handle.LockTable(ltS,
                      SelectTrialTableLockRetries, SelectTrialTableLockDelay)
      else
        Result := False;
      FIsLocked := Result;
    end
  else
    begin
      Result := True;
      if (FLeftAO <> nil) then
        Result := Result and FLeftAO.LockTables;
      if (FRightAO <> nil) then
       if (Result) then
         Result := Result and FRightAO.LockTables;
    end;
end;// LockTables


//------------------------------------------------------------------------------
// UnlockTables
//------------------------------------------------------------------------------
function TABSAO.UnlockTables: Boolean;
begin
  if (FIsAOTable) then
    begin
      if (FIsLocked) then
        if (ResultDataset.Active) then
           begin
             Result := TABSTable(ResultDataset).Handle.UnlockTable(ltS, True);
             FIsLocked := not Result;
           end
        else
          Result := False
      else
        Result := True;
    end
  else
    begin
      Result := True;
      if (FLeftAO <> nil) then
        Result := Result and FLeftAO.UnlockTables;
      if (FRightAO <> nil) then
        Result := Result and FRightAO.UnlockTables;
    end;
end;// UnlockTables


//------------------------------------------------------------------------------
// OpenTables
//------------------------------------------------------------------------------
function TABSAO.OpenTables: Boolean;
begin
  if (FIsAOTable) then
    begin
      try
        ResultDataset.Open;
        Result := True;
      except
        ResultDataset.Close;
        Result := False;
      end;
    end
  else
    begin
      Result := True;
      if (FLeftAO <> nil) then
        Result := Result and FLeftAO.OpenTables;
      if (FRightAO <> nil) then
        Result := Result and FRightAO.OpenTables;
    end;
end;// OpenTables


//------------------------------------------------------------------------------
// CloseTables
//------------------------------------------------------------------------------
function TABSAO.CloseTables: Boolean;
begin
  if (FIsAOTable) then
    begin
      try
        ResultDataset.Close;
        Result := True;
      except
        Result := False;
      end;
    end
  else
    begin
      Result := True;
      if (FLeftAO <> nil) then
        Result := Result and FLeftAO.CloseTables;
      if (FRightAO <> nil) then
        Result := Result and FRightAO.CloseTables;
    end;
end;// CloseTables


//------------------------------------------------------------------------------
// set filter
//------------------------------------------------------------------------------
procedure TABSAO.SetFilter(FilterExpr: TObject);
begin
 if (FFilterExpr = nil) then
   FFilterExpr := FilterExpr
 else
   begin
     TABSExpression(FFilterExpr).AddAndMoveNodesFromExpression(TABSExpression(FilterExpr));
     FilterExpr.Free;
   end;
end; // SetFilter


//------------------------------------------------------------------------------
// for SELECT INTO optimization
//------------------------------------------------------------------------------
procedure TABSAO.SetResultTable(InMemory, Immediate: Boolean; TableName: string; DatabaseName: string);
begin
  FResultInMemory := InMemory;
  FResultImmediate := Immediate;
  FResultTableName := TableName;
  FResultDatabaseName := DatabaseName;
end; // SetResultTable


//------------------------------------------------------------------------------
// sets Top row count
//------------------------------------------------------------------------------
procedure TABSAO.SetTopRowCount(FirstRowNo, TopRowCount: integer);
begin
  if (not FDistinctApplied) and
     ((FResultIndexFieldsList = nil) or
      ((FResultIndexFieldsList <> nil) and (FResultIndexFieldsList.Count = 0))) then
    begin
      FTopRowCount := TopRowCount;
      if (FirstRowNo < 0) then
        FFirstRowNo := 1
      else
        FFirstRowNo := FirstRowNo;
    end;
end;// SetTopRowCount


//------------------------------------------------------------------------------
// applies distinct
//------------------------------------------------------------------------------
procedure TABSAO.ApplyDistinct(DistinctFields: string);
var
  DistinctFieldList: TStringList;

procedure ApplyDistinctToChild(AO: TABSAO);
var
  ChildDistinctFields: string;
  i: integer;
begin
  if not (AO is TABSAOTable) then
    begin
      ChildDistinctFields := '';
      for i := 0 to FFieldCount-1 do
        if (FFieldLinks[i].AO = AO) and (not FFieldLinks[i].IsHidden) then
          begin
            if (ChildDistinctFields <> '') then
              ChildDistinctFields := ChildDistinctFields+';';
            ChildDistinctFields := ChildDistinctFields +
              TABSAO(FFieldLinks[i].AO).FFieldLinks[FFieldLinks[i].FieldNo].FieldName;
          end;
      if (ChildDistinctFields <> '') then
        AO.ApplyDistinct(ChildDistinctFields);
    end;
end;

procedure FillDistinctFieldsMap;
var
  i,j: integer;
begin
  SetLength(FDistinctFieldsMap, DistinctFieldList.Count);
  for i := 0 to DistinctFieldList.Count-1 do
    for j := 0 to FFieldCount-1 do
      if (AnsiUpperCase(FFieldLinks[j].FieldName) = AnsiUpperCase(DistinctFieldList[i])) then
        begin
          FDistinctFieldsMap[i] := j;
          break;
        end;
end;

begin
  DistinctFieldList := TStringList.Create;
  try
    FillFieldNames(DistinctFieldList,DistinctFields);
    FDistinctApplied := (DistinctFieldList.Count > 0);
    if (FDistinctApplied) then
      begin
        FDistinctFields := DistinctFields;
        FDistinctFieldCount := DistinctFieldList.Count;
        FillDistinctFieldsMap;
        // bug-fix 4.85 - incorrect when condition on hidden join fields
        if (FFilterExpr = nil) then
          if (Self is TABSAOJoin) then
            begin
              if (FLeftAO <> nil) then
                ApplyDistinctToChild(FLeftAO);
              if (FRightAO <> nil) then
                ApplyDistinctToChild(FRightAO);
            end;
      end;
  finally
    DistinctFieldList.Free;
  end;
end; // ApplyDistinct


//------------------------------------------------------------------------------
// sets projection for other TABSAO
//------------------------------------------------------------------------------
procedure TABSAO.SetResultFields(var FieldRefs: array of TABSSelectListItem;
                                  bDistinct: Boolean);
var i,j,k,x,res: integer;
    fname,tname: string;
    aDistinctFields: string;
begin
 aDistinctFields := '';
 j := Length(FieldRefs);
 if (j <= 0) then
  begin
   FieldExists('*','',true,FResultFieldsOrder);
  end
 else
  for i := 0 to j-1 do
   begin
    if (FieldRefs[i].AllFields) then
     fname := '*'
    else
     fname := FieldRefs[i].FieldName;
    tname := FieldRefs[i].TableName;
    if (FieldRefs[i].IsExpression) then
      if ((not TABSExpression(FieldRefs[i].ValueExpr).IsAggregated) or (FIsAOGroupBy)) then
       begin
        inc(FFieldCount);
        SetLength(FFieldLinks,FFieldCount);
        FFieldLinks[FFieldCount-1].Expr := FieldRefs[i].ValueExpr;
        TABSExpression(FFieldLinks[FFieldCount-1].Expr).AssignAO(self, nil, nil);
        // create new field for this expression
        if (FieldRefs[i].Pseudonym <> '') then
         // pseudonim specified
         FFieldLinks[FFieldCount-1].FieldName := FieldRefs[i].Pseudonym
        else
         // random name
         FFieldLinks[FFieldCount-1].FieldName := GetTemporaryName(ABSExpressionFieldName);
        FFieldLinks[FFieldCount-1].DisplayName := FFieldLinks[FFieldCount-1].FieldName;
        // get field type
        FFieldLinks[FFieldCount-1].FieldType :=
          TABSExpression(FieldRefs[i].ValueExpr).GetDataType;
        // get field size
        FFieldLinks[FFieldCount-1].FieldSize :=
          TABSExpression(FieldRefs[i].ValueExpr).GetDataSize;
        // get field precision
        FFieldLinks[FFieldCount-1].FieldPrecision :=
          TABSExpression(FieldRefs[i].ValueExpr).GetPrecision;
        // AO
        FFieldLinks[FFieldCount-1].AO := self;
        FFieldLinks[FFieldCount-1].Dataset := nil;
        FFieldLinks[FFieldCount-1].IsHidden := false;
        FFieldLinks[FFieldCount-1].IsExpression := true;
        FFieldLinks[FFieldCount-1].IsAggregate :=
          TABSExpression(FieldRefs[i].ValueExpr).IsAggregated;
        FExpressionsExists := True;
        FResultFieldsOrder.Append(FFieldCount-1);
        // update fieldrefs
        if ((not FIsAOGroupBy) and (not TABSExpression(FieldRefs[i].ValueExpr).IsAggregated)) then
          begin
            FieldRefs[i].IsExpression := False;
            FieldRefs[i].ValueExpr := nil;
            FieldRefs[i].FieldName := FFieldLinks[FFieldCount-1].FieldName;
          end
        else
          FieldRefs[i].ValueExpr := nil;

        continue;
       end; // Expression

    if (fname <> '') then
      begin
        res := FieldExists(fname,tname,true,FResultFieldsOrder);
        if (res = 1) then
         begin
          x := FResultFieldsOrder.Items[FResultFieldsOrder.ItemCount-1];
         if (FieldRefs[i].Pseudonym <> '') then
          // set pseudonym
           FFieldLinks[x].DisplayName := FieldRefs[i].Pseudonym
         else
          // use name like it was written in query
          if (fname <> '*') then
            FFieldLinks[x].DisplayName := fname;
         end // res = 0
        else
        if (res = 0) then
          raise EABSException.Create(10305, ErrorLCannotFindField, [fname])
    // result dataset
        else
         if (res > 1) then
          if (fname <> '*') then
           begin
            for x := 0 to res-2 do
             FResultFieldsOrder.Delete(FResultFieldsOrder.ItemCount-1);
            x := FResultFieldsOrder.Items[FResultFieldsOrder.ItemCount-1];
            if (FieldRefs[i].Pseudonym <> '') then
             // set pseudonym
             FFieldLinks[x].DisplayName := FieldRefs[i].Pseudonym
            else
             // use name like it was written in query
             if (fname <> '*') then
              FFieldLinks[x].DisplayName := fname;
           end; // DUPLICATE NAMES IN SELECT LIST REMOVED
      end;
   end;
 for i := 0 to FResultFieldsOrder.ItemCount - 1 do
  begin
   k := FResultFieldsOrder.Items[i];
   if (bDistinct) then
    begin
     if (aDistinctFields = '') then
      aDistinctFields := FFieldLinks[k].FieldName
     else
      aDistinctFields := aDistinctFields + ';' + FFieldLinks[k].FieldName;
    end;

   if (FFieldLinks[k].DisplayName = '') then
    FFieldLinks[k].DisplayName :=
      FFieldLinks[k].FieldName;
  end;

  if (bDistinct) then
   ApplyDistinct(aDistinctFields);
 FHasSetResultFields := True;
end;// SetResultFields


//------------------------------------------------------------------------------
// mapping function - return number of found fields and found field No
// also optionally unhides fields in AO
//------------------------------------------------------------------------------
function TABSAO.FieldExists(
                  FieldName, TableName: string;
                  Unhide: Boolean;
                  FieldNumbers: TABSIntegerArray = nil;
                  UnhideChildrenOnly: Boolean = false;
                  ScanOnlyVisibleFields: Boolean = false
                  ): Integer;
var i,j,k: integer;
    fname: string;
    tempArray: TABSIntegerArray;
begin
 result := 0;
 if (FIsAOTable) then
   if (TableName <> '') then
    if (AnsiUpperCase(TableName) <> AnsiUpperCase(FTableName)) and
      (AnsiUpperCase(TableName) <> AnsiUpperCase(FTableAlias)) then
     Exit; // another table

 // all fields
 if (FieldName = '*') then
  begin
   // unhide or field numbers
   tempArray := TABSIntegerArray.Create(0,1,100);
   if (FLeftAO <> nil) then
     FLeftAO.FieldExists(FieldName,TableName,Unhide, tempArray);

   for i := 0 to FFieldCount-1 do
    if (FIsAOTable) or
       ((AnsiUpperCase(TableName) = AnsiUpperCase(FTableName)) and
       (FFieldLinks[i].IsExpression)) or
       ((FLeftAO <> nil) and
          (FFieldLinks[i].AO = FLeftAO) and
          (tempArray.IsValueExists(FFieldLinks[i].FieldNo))) then
     begin
      inc(result);
      if (FieldNumbers <> nil) then
       FieldNumbers.Append(i);
      // group by does not needs in unhiding
      // it will cause hidden fields to be appeared in result dataset
      if (Unhide) and (not UnhideChildrenOnly) then
       if (not FIsAOGroupBy) then
        FFieldLinks[i].IsHidden := false;
     end;
   tempArray.SetSize(0);

   if (FRightAO <> nil) then
    begin
      FRightAO.FieldExists(FieldName,TableName,Unhide, tempArray);
      for i := 0 to FFieldCount-1 do
      if ((FRightAO <> nil) and
            (FFieldLinks[i].AO = FRightAO) and
            (tempArray.IsValueExists(FFieldLinks[i].FieldNo))) then
       begin
        inc(result);
        if (FieldNumbers <> nil) then
         FieldNumbers.Append(i);
        // group by does not needs in unhiding
        // it will cause hidden fields to be appeared in result dataset
        if (Unhide) and (not UnhideChildrenOnly) then
         FFieldLinks[i].IsHidden := false;
       end;
    end;
   tempArray.Free;
   Exit;
  end; // all fields

 fname := AnsiUpperCase(FieldName);
   // not TABSAOTable
   tempArray := nil;
   if (FieldNumbers <> nil) then
    tempArray := TABSIntegerArray.Create(0,1,100);
   if (FLeftAO <> nil) then
    begin
     FLeftAO.FieldExists(FieldName,TableName,Unhide,tempArray);
     for i := 0 to tempArray.ItemCount-1 do
      begin
       k := tempArray.Items[i];
       for j := 0 to FFieldCount-1 do
        if (FFieldLinks[j].AO = FLeftAO) then
         if (FFieldLinks[j].FieldNo = k) and
            ((not ScanOnlyVisibleFields) or (not FFieldLinks[j].IsHidden)) then
          begin
           Inc(Result);
           // return FieldNo in current FFieldLinks
           if (FieldNumbers <> nil) then
             FieldNumbers.Append(j);
           // group by does not needs in unhiding
           // it will cause hidden fields to be appeared in result dataset
           if (Unhide) and (not UnhideChildrenOnly) then
            FFieldLinks[j].IsHidden := false;
          end;
      end;
     tempArray.SetSize(0); // empty temp array
    end; // LeftAO
   if (FRightAO <> nil) then
    begin
     FRightAO.FieldExists(FieldName,TableName,Unhide,tempArray);
     for i := 0 to tempArray.ItemCount-1 do
      begin
       k := tempArray.Items[i];
       for j := 0 to FFieldCount-1 do
        if (FFieldLinks[j].AO = FRightAO) then
         if (FFieldLinks[j].FieldNo = k) and
            ((not ScanOnlyVisibleFields) or (not FFieldLinks[j].IsHidden)) then
          begin
           Inc(Result);
           // return FieldNo in current FFieldLinks
           if (FieldNumbers <> nil) then
             FieldNumbers.Append(j);
           // group by does not needs in unhiding
           // it will cause hidden fields to be appeared in result dataset
           if (Unhide) and (not UnhideChildrenOnly) then
            FFieldLinks[j].IsHidden := false;
          end;
      end;
      tempArray.SetSize(0);
    end; // RightAO
   if (tempArray <> nil) then
    tempArray.Free;

   // find field
   for i := 0 to FFieldCount-1 do
    if (AnsiUpperCase(FFieldLinks[i].FieldName) = fname) or
       (AnsiUpperCase(FFieldLinks[i].DisplayName) = fname) then
     if (TableName = '') or
        (AnsiUpperCase(TableName) = AnsiUpperCase(FTableName)) or
        (AnsiUpperCase(TableName) = AnsiUpperCase(FTableAlias)) then
      if ((not ScanOnlyVisibleFields) or (not FFieldLinks[i].IsHidden)) then
        begin
         if (FieldNumbers <> nil) then
          if (not FieldNumbers.IsValueExists(i)) then
           begin
            FieldNumbers.Append(i);
            inc(result);
           end;
         // group by does not needs in unhiding
         // it will cause hidden fields to be appeared in result dataset
         if (Unhide) and (not UnhideChildrenOnly) then
          FFieldLinks[i].IsHidden := false;
         break;
        end;
end;// FieldExists


//------------------------------------------------------------------------------
// FindFieldInFieldLinks
//------------------------------------------------------------------------------
function TABSAO.FindFieldInFieldLinks(FieldName: string; var FieldNo: Integer): Boolean;
var
  i: Integer;
begin
  Result := False;
  for i := 0 to FFieldCount-1 do
    if (AnsiUpperCase(FFieldLinks[i].FieldName) = AnsiUpperCase(FieldName)) or
       (AnsiUpperCase(FFieldLinks[i].DisplayName) = AnsiUpperCase(FieldName)) then
      begin
        FieldNo := i;
        Result := True;
        break;
      end;
end;// FindFieldInFieldLinks


//------------------------------------------------------------------------------
// return FieldName from AOTable for specified internal field
//------------------------------------------------------------------------------
function TABSAO.GetFieldName(FieldNo: Integer; ApplyOrderBy: Boolean = False): string;
begin
 if (FieldNo < 0) or (FieldNo >= FieldCount) then
  raise EABSException.Create(10306,ErrorLInvalidFieldNumber,[FieldNo,FieldCount]);
 Result := '';
 if (ApplyOrderBy) then
  begin
   // apply order by
   if (FFieldLinks[FieldNo].Dataset <> nil) then
    begin
     if (FFieldLinks[FieldNo].Dataset = FResultDataset) then
      Result := FFieldLinks[FieldNo].FieldName
     else
      Result := FFieldLinks[FieldNo].Dataset.Fields[FFieldLinks[FieldNo].FieldNo].FieldName;
    end
   else
    begin
      Result := FFieldLinks[FieldNo].FieldName;
    end;
  end
 else
  begin
   // set field names for join
   if (FFieldLinks[FieldNo].Dataset <> nil) then
    begin
     if (FFieldLinks[FieldNo].Dataset = FResultDataset) then
      Result := FFieldLinks[FieldNo].FieldName
     else
      Result := FFieldLinks[FieldNo].Dataset.Fields[FFieldLinks[FieldNo].FieldNo].FieldName
    end
   else
    Result := TABSAO(FFieldLinks[FieldNo].AO).GetFieldName(FFieldLinks[FieldNo].FieldNo)
  end;

end; // GetFieldName


//------------------------------------------------------------------------------
// return FieldName from AOTable for specified internal field
//------------------------------------------------------------------------------
function TABSAO.GetFieldNameByColumnName(ColumnName: string): string;
var i:    Integer;
    name: string;
begin
 Result := '';
 name := AnsiUpperCase(ColumnName);
 for i := 0 to FFieldCount - 1 do
  if ((name = AnsiUpperCase(FFieldLinks[i].FieldName)) or
      (name = AnsiUpperCase(FFieldLinks[i].DisplayName))) then
   begin
    Result := FFieldLinks[i].FieldName;
    break;
   end;
end; // GetFieldName


//------------------------------------------------------------------------------
// GetFieldNameByColumnNo
//------------------------------------------------------------------------------
function TABSAO.GetFieldNameByColumnNo(ColumnNo: Integer): string;
begin
  Result := FFieldLinks[ColumnNo].FieldName;
end;// GetFieldNameByColumnNo


//------------------------------------------------------------------------------
// GetFieldNameByVisibleNumber
//------------------------------------------------------------------------------
function TABSAO.GetFieldNameByVisibleNumber(VisibleFieldNo: Integer): string;
begin
  if (VisibleFieldNo < FResultFieldsOrder.ItemCount) then
    Result := FFieldLinks[FResultFieldsOrder.Items[VisibleFieldNo]].DisplayName
  else
    raise EABSException.Create(20243, ErrorAInvalidFieldNumberInOrderBy, [VisibleFieldNo+1]);
end;// GetFieldNameByVisibleNumber


//------------------------------------------------------------------------------
// GetFieldNoByVisibleNumber
//------------------------------------------------------------------------------
function TABSAO.GetFieldNoByVisibleNumber(VisibleFieldNo: Integer): Integer;
begin
  if (FResultFieldsOrder.ItemCount > 0) then
    Result := FResultFieldsOrder.Items[VisibleFieldNo]
  else
    Result := VisibleFieldNo;
end;// GetFieldNoByVisibleNumber


//------------------------------------------------------------------------------
// return field value
//------------------------------------------------------------------------------
procedure TABSAO.GetFieldValue(
                        Value:          TABSVariant;
                        FieldNo:        Integer;
                        bCopy:          Boolean = False;
                        AccessToHidden: Boolean = False
                              );
var
  TmpValue: TABSVariant;
  UseDirectFieldAccess: Boolean;
begin
  if ((FieldNo < 0) or (FieldNo >= FieldCount)) then
    raise EABSException.Create(10307, ErrorLInvalidFieldNumber,
                               [FieldNo,FieldCount]);

  Value.SetNull;

  // AO = Dataset = nil
  if ((FFieldLinks[FieldNo].Dataset = nil) and (FFieldLinks[FieldNo].AO = nil)) then
    raise EABSException.Create(10309, ErrorLInvalidFieldLink, [FTableName,FieldNo]);

  // hidden field
  if not AccessToHidden then
   if (FFieldLinks[FieldNo].IsHidden) then
     raise EABSException.Create(10310,ErrorLCannotAccessHiddenField,
                    [FTableName,FFieldLinks[FieldNo].FieldName,
                     FFieldLinks[FieldNo].DisplayName, FieldNo]);

 if (FFieldLinks[FieldNo].IsExpression) then
   begin
     TmpValue := TABSExpression(FFieldLinks[FieldNo].Expr).GetValue;
     if (TmpValue <> nil) then
       Value.Assign(TmpValue, False);
   end // Expression
 else
 if (FFieldLinks[FieldNo].Dataset <> nil) then
   begin
     if (not (FFieldLinks[FieldNo].Dataset.Eof and FFieldLinks[FieldNo].Dataset.Bof)) then
       begin
         UseDirectFieldAccess := not (Self is TABSAOUnion) or
                                (not FIsRootAO and FHasSetResultFields and not FIsAOTable);
         TABSTable(FFieldLinks[FieldNo].Dataset).GetFieldValue(value,
                               FFieldLinks[FieldNo].FieldNo, UseDirectFieldAccess);
       end;
   end
 else
   begin
     if (FLeftAONull and (FFieldLinks[FieldNo].AO = FLeftAO)) or
        (FRightAONull and (FFieldLinks[FieldNo].AO = FRightAO)) then
       begin
         // return null value
         Exit;
       end;
     TABSAO(FFieldLinks[FieldNo].AO).GetFieldValue(value,
                          FFieldLinks[FieldNo].FieldNo, bCopy, AccessToHidden);
   end;
end; // GetFieldValue


//------------------------------------------------------------------------------
// get field type
//------------------------------------------------------------------------------
function TABSAO.GetFieldType(FieldNo: Integer): TABSAdvancedFieldType;
begin
  if ((FieldNo < 0) or (FieldNo >= FieldCount)) then
    raise EABSException.Create(10376, ErrorLInvalidFieldNumber,
                               [FieldNo,FieldCount]);
  Result := FFieldLinks[FieldNo].FieldType;
end; // GetFieldType


//------------------------------------------------------------------------------
// get field size
//------------------------------------------------------------------------------
function TABSAO.GetFieldSize(FieldNo: Integer): Integer;
begin
  if ((FieldNo < 0) or (FieldNo >= FieldCount)) then
    raise EABSException.Create(10377, ErrorLInvalidFieldNumber,
                               [FieldNo,FieldCount]);
  Result := FFieldLinks[FieldNo].FieldSize;
end; // GetFieldSize


//------------------------------------------------------------------------------
// get field precision
//------------------------------------------------------------------------------
function TABSAO.GetFieldPrecision(FieldNo: Integer): Integer;
begin
  if ((FieldNo < 0) or (FieldNo >= FieldCount)) then
    raise EABSException.Create(10378, ErrorLInvalidFieldNumber,
                               [FieldNo,FieldCount]);
  Result := FFieldLinks[FieldNo].FieldPrecision;
end; // GetFieldPrecision


//------------------------------------------------------------------------------
// CopyFieldValue
//------------------------------------------------------------------------------
procedure TABSAO.CopyFieldValue(SrcFieldNo,DestFieldNo: Integer);
var
  UseDirectFieldAccess: Boolean;
  SrcFieldDataset: TDataset;
  SrcFieldDatasetFieldNo: Integer;
begin

 SrcFieldDatasetFieldNo := SrcFieldNo;
 if (not FFieldLinks[SrcFieldNo].IsHidden) and
    (not FFieldLinks[SrcFieldNo].IsExpression) and
    (GetFieldDatsetAndFieldNo(SrcFieldDataset, SrcFieldDatasetFieldNo)) then
   begin
     UseDirectFieldAccess := not (Self is TABSAOUnion) or
                            (not FIsRootAO and FHasSetResultFields and not FIsAOTable);
     TABSDataset(SrcFieldDataset).CopyFieldValue(
                 SrcFieldDatasetFieldNo, UseDirectFieldAccess,
                 DestFieldNo, TABSDataset(FResultDataset));
   end
 else
   begin
     // get field value
     GetFieldValue(FValue,SrcFieldNo,false,true);
     // set value
     TABSDataset(FResultDataset).SetFieldValue(FValue,DestFieldNo, false);
     FValue.SetNull;
   end;
end;// CopyFieldValue


//------------------------------------------------------------------------------
// GetFieldDatsetAndFieldNo
//------------------------------------------------------------------------------
function TABSAO.GetFieldDatsetAndFieldNo(var SrcFieldDatset: TDataset;
                                  var SrcFieldDatasetFieldNo: Integer): Boolean;
var
  AO: TABSAO;
  FieldNo: Integer;
begin
  Result := False;
  AO := self;
  while (AO <> nil) do
    begin
      if (AO.FFieldLinks[SrcFieldDatasetFieldNo].Dataset <> nil) then
        begin
          SrcFieldDatset := AO.FFieldLinks[SrcFieldDatasetFieldNo].Dataset;
          SrcFieldDatasetFieldNo := AO.FFieldLinks[SrcFieldDatasetFieldNo].FieldNo;
          Result := True;
          break;
        end
      else
        begin
         if (AO.FLeftAONull and (AO.FFieldLinks[SrcFieldDatasetFieldNo].AO = AO.FLeftAO)) or
            (AO.FRightAONull and (AO.FFieldLinks[SrcFieldDatasetFieldNo].AO = AO.FRightAO)) then
           begin
             // return null value
             break;
           end
         else
           begin
             FieldNo := SrcFieldDatasetFieldNo;
             SrcFieldDatasetFieldNo := AO.FFieldLinks[FieldNo].FieldNo;
             AO := AO.FFieldLinks[FieldNo].AO;
           end;
        end;
    end;
end;// GetFieldDatsetAndFieldNo


//------------------------------------------------------------------------------
// set distinct
//------------------------------------------------------------------------------
procedure TABSAO.InternalSetDistinct;
var
  DistinctFieldsList,
  IndexFieldsList,
  IndexAscDescFieldsList,
  IndexCaseInsFieldsList: TStringList;
  i: Integer;
begin
  DistinctFieldsList := TStringList.Create;
  IndexFieldsList := TStringList.Create;
  IndexAscDescFieldsList := TStringList.Create;
  IndexCaseInsFieldsList := TStringList.Create;
  try
    if (FResultIndexFieldsList <> nil) then
      for i := 0 to FResultIndexFieldsList.Count - 1 do
        begin
          IndexFieldsList.Add(FResultIndexFieldsList[i]);
          IndexAscDescFieldsList.Add(FResultIndexAscDescFieldsList[i]);
          IndexCaseInsFieldsList.Add(FResultIndexCaseInsFieldsList[i]);
        end;
    FillFieldNames(DistinctFieldsList,FDistinctFields);
    for i := 0 to DistinctFieldsList.Count - 1 do
      if (IndexFieldsList.IndexOf(DistinctFieldsList[i]) < 0) then
        begin
          IndexFieldsList.Add(DistinctFieldsList[i]);
          IndexAscDescFieldsList.Add(ABS_ASC);
          IndexCaseInsFieldsList.Add(ABS_CASE);
        end;
    TABSTable(FResultDataset).ApplyDistinct(IndexFieldsList,
        IndexAscDescFieldsList,IndexCaseInsFieldsList);
  finally
    IndexFieldsList.Free;
    IndexAscDescFieldsList.Free;
    IndexCaseInsFieldsList.Free;
    DistinctFieldsList.Free;
  end;
end; // InternalSetDistinct


//------------------------------------------------------------------------------
// sets index
//------------------------------------------------------------------------------
procedure TABSAO.InternalSetIndex(ToClearFields: Boolean = True);
var
  IsCreated: Boolean;
begin
 if (FResultDataset = nil) then
  raise EABSException.Create(10308,ErrorLNilPointer);

 if ((FResultIndexFieldsList <> nil) and (FResultIndexFieldsList.Count <> 0) and
     (TABSTable(FResultDataset).IndexName = '')) then
  begin
   try
     TABSTable(FResultDataset).IndexName :=
        TABSTable(FResultDataset).FindOrCreateIndex(
          FResultIndexFieldsList,FResultIndexAscDescFieldsList,FResultIndexCaseInsFieldsList,IsCreated);
   finally
    if (ToClearFields) then
     FreeResultIndexLists;
   end;
  end;
end;


//------------------------------------------------------------------------------
// create result index lists
//------------------------------------------------------------------------------
procedure TABSAO.CreateResultIndexLists(ToClear: Boolean = True);
begin
 if (FResultIndexFieldsList = nil) then
   FResultIndexFieldsList := TStringList.Create
 else
  if (ToClear) then
   FResultIndexFieldsList.Clear;
 if (FResultIndexAscDescFieldsList = nil) then
   FResultIndexAscDescFieldsList := TStringList.Create
 else
  if (ToClear) then
   FResultIndexAscDescFieldsList.Clear;
 if (FResultIndexCaseInsFieldsList = nil) then
   FResultIndexCaseInsFieldsList := TStringList.Create
 else
  if (ToClear) then
   FResultIndexCaseInsFieldsList.Clear;
end; // CreateResultIndexLists


//------------------------------------------------------------------------------
// free and nil result index lists
//------------------------------------------------------------------------------
procedure TABSAO.FreeResultIndexLists;
begin
 if (FResultIndexFieldsList <> nil) then
   FResultIndexFieldsList.Free;
 if (FResultIndexAscDescFieldsList <> nil) then
   FResultIndexAscDescFieldsList.Free;
 if (FResultIndexCaseInsFieldsList <> nil) then
   FResultIndexCaseInsFieldsList.Free;
 FResultIndexFieldsList := nil;
 FResultIndexAscDescFieldsList := nil;
 FResultIndexCaseInsFieldsList := nil;
end; // FreeResultIndexLists


//------------------------------------------------------------------------------
// sets index
//------------------------------------------------------------------------------
procedure TABSAO.SetIndex(IndexFieldNames, DescFields, CaseInsensitiveFields: string);
begin
 FreeResultIndexLists;
 CreateResultIndexLists(True);
 ConvertIndexFieldNamesToLists(
      IndexFieldNames,DescFields,CaseInsensitiveFields,
      FResultIndexFieldsList,FResultIndexAscDescFieldsList,
      FResultIndexCaseInsFieldsList);
 if (FDistinctApplied) then
  ApplyDistinct(FDistinctFields);     
end; // SetIndex

//------------------------------------------------------------------------------
// AddFieldLink
//------------------------------------------------------------------------------
procedure TABSAO.AddFieldLink(FieldLinkNo: Integer; SkipHiddenFields: Boolean = True);
begin
   if (FLeftAO.FHasSetResultFields and FLeftAO.FFieldLinks[FieldLinkNo].IsHidden and SkipHiddenFields) then
     exit;

   SetLength(FFieldLinks,FFieldCount+1);
   FFieldLinks[FFieldCount].AO := FLeftAO;
   FFieldLinks[FFieldCount].Dataset := nil;
   FFieldLinks[FFieldCount].FieldNo := FieldLinkNo;
   FFieldLinks[FFieldCount].IsHidden := FLeftAO.FFieldLinks[FieldLinkNo].IsHidden;
   if (FLeftAO.FHasSetResultFields and (FLeftAO.FFieldLinks[FieldLinkNo].DisplayName <> '')) then
     FFieldLinks[FFieldCount].FieldName := FLeftAO.FFieldLinks[FieldLinkNo].DisplayName
   else
     FFieldLinks[FFieldCount].FieldName := FLeftAO.FFieldLinks[FieldLinkNo].FieldName;
   FFieldLinks[FFieldCount].FieldType := FLeftAO.FFieldLinks[FieldLinkNo].FieldType;
   FFieldLinks[FFieldCount].FieldSize := FLeftAO.FFieldLinks[FieldLinkNo].FieldSize;
   FFieldLinks[FFieldCount].FieldPrecision := FLeftAO.FFieldLinks[FieldLinkNo].FieldPrecision;
   FFieldLinks[FFieldCount].IsExpression := false;
   FFieldLinks[FFieldCount].IsAggregate := false;
   FFieldLinks[FFieldCount].BLOBCompressionAlgorithm := FLeftAO.FFieldLinks[FieldLinkNo].BLOBCompressionAlgorithm;
   FFieldLinks[FFieldCount].BLOBCompressionMode := FLeftAO.FFieldLinks[FieldLinkNo].BLOBCompressionMode;
   FFieldLinks[FFieldCount].BLOBBlockSize := FLeftAO.FFieldLinks[FieldLinkNo].BLOBBlockSize;
   Inc(FFieldCount);
end;// AddFieldLink


////////////////////////////////////////////////////////////////////////////////
//
// TABSAOTable
//
////////////////////////////////////////////////////////////////////////////////


constructor TABSAOTable.Create(
                      TableName:  string;
                      TableAlias: string;
                      DB: TObject;
                      Table: TDataset
                      );
var i: integer;
begin
 InternalCreate(nil,nil,TableName,TableAlias);
 FDatabase := DB;
 FResultDataset.Free;
 FResultDataset := Table;
 if (DB <> nil) then
   FDisableTempFiles := TABSDatabase(DB).DisableTempFiles
 else
   FDisableTempFiles := False;

 FIsAOTable := true;
 try
  FResultDataset.Open;
 except
   on E: Exception do
    raise EABSException.Create(10303,ErrorLCannotOpenTableInSQL,[TableName,Word(TABSTable(FResultDataset).InMemory),E.Message]);
 end;
 FFieldCount := TABSTable(FResultDataset).AdvFieldDefs.Count;
 SetLength(FFieldLinks,FFieldCount);
 for i := 0 to FFieldCount-1 do
  begin
   FFieldLinks[i].AO := nil;
   FFieldLinks[i].Dataset := FResultDataset;
   FFieldLinks[i].FieldNo := i;
   FFieldLinks[i].IsHidden := True;
   FFieldLinks[i].IsExpression := False;
   FFieldLinks[i].IsAggregate  := False;
   FFieldLinks[i].FieldName := TABSTable(FResultDataset).AdvFieldDefs.Items[i].Name;
   FFieldLinks[i].DisplayName := '';
   FFieldLinks[i].FieldType := TABSTable(FResultDataset).AdvFieldDefs.Items[i].DataType;
   FFieldLinks[i].FieldSize := TABSTable(FResultDataset).AdvFieldDefs.Items[i].Size;
   FFieldLinks[i].BLOBCompressionAlgorithm :=
    Byte(TABSTable(FResultDataset).AdvFieldDefs.Items[i].BLOBCompressionAlgorithm);
   FFieldLinks[i].BLOBCompressionMode := TABSTable(FResultDataset).AdvFieldDefs.Items[i].BLOBCompressionMode;
   FFieldLinks[i].BLOBBlockSize := TABSTable(FResultDataset).AdvFieldDefs.Items[i].BLOBBlockSize;
  end;
end; // Create


//------------------------------------------------------------------------------
// Destroy
//------------------------------------------------------------------------------
destructor TABSAOTable.Destroy;
begin
//  if (FDatabase <> nil) then
//    FDatabase.Free;
  inherited;
end;// Destroy


//------------------------------------------------------------------------------
// Execute
//------------------------------------------------------------------------------
procedure TABSAOTable.Execute(IsRootAO: Boolean; ParentQueryAO: TABSAO; ParentCursor: TABSCursor);
begin
  // filter
  if (FResultIndexFieldsList <> nil) and (FResultIndexFieldsList.Count <> 0) then
    InternalSetIndex(not FDistinctApplied);
  if (FDistinctApplied) then
    begin
     InternalSetDistinct;
     FreeResultIndexLists;
    end;
  TABSDataset(FResultDataset).SetSQLFilter(FFilterExpr, ParentQueryAO, ParentCursor);

  inherited Execute(IsRootAO,ParentQueryAO,ParentCursor);
end;// Execute


//------------------------------------------------------------------------------
// sets projection for TABSAOTable
//------------------------------------------------------------------------------
procedure TABSAOTable.SetResultFields(var FieldRefs: array of TABSSelectListItem;
          bDistinct: Boolean);
var i,j: integer;
    FieldList: TStringList;
    AliasList: TStringList;
    tableName: string;
    tableAlias:string;
    tName:     string;
begin
 j := Length(FieldRefs);
 if (j <= 0) then
  begin
   inherited SetResultFields(FieldRefs,bDistinct);
   Exit;
  end;
 FieldList := TStringList.Create;
 AliasList := TStringList.Create;
 tableName := AnsiUpperCase(FTableName);
 tableAlias := AnsiUpperCase(FTableAlias);
 for i := 0 to j-1 do
  begin
   tName := AnsiUpperCase(FieldRefs[i].TableName);
   if (tName <> '') then
    if (tableName <> tName) and (tableAlias <> tName) then
     raise EABSException.Create(30327,ErrorLInvalidTableNameInReference,
              [tableName,tableAlias,tName]);
   if (FieldRefs[i].AllFields) then
     begin
      // no projection
      FieldList.Free;
      AliasList.Free;
      Exit;
     end;
   FieldList.Add(FieldRefs[i].FieldName);
   AliasList.Add(FieldRefs[i].Pseudonym);
  end;
 inherited SetResultFields(FieldRefs,bDistinct);
 TABSTable(FResultDataset).ApplyProjection(FieldList,AliasList);
 FieldList.Free;
 AliasList.Free;
end; // SetResultsFields


//------------------------------------------------------------------------------
// TransferIndexToParentIfNeeded
//------------------------------------------------------------------------------
function TABSAOTable.TransferIndexToParentIfNeeded(var FieldNamesList, AscDescList, CaseSensitivityList: TStringList): Boolean;
begin
 Result := False;
 if (FResultDataset = nil) then
  raise EABSException.Create(10513,ErrorLNilPointer);

 if ((FResultIndexFieldsList <> nil) and (FResultIndexFieldsList.Count <> 0) and
     (TABSTable(FResultDataset).IndexName = '')) then
  begin
    if (not TABSTable(FResultDataset).IndexExists(FResultIndexFieldsList,FResultIndexAscDescFieldsList,FResultIndexCaseInsFieldsList)) then
      begin
        FieldNamesList := FResultIndexFieldsList;
        FResultIndexFieldsList := nil;
        AscDescList := FResultIndexAscDescFieldsList;
        FResultIndexAscDescFieldsList := nil;
        CaseSensitivityList := FResultIndexCaseInsFieldsList;
        FResultIndexCaseInsFieldsList := nil;
        Result := True;
      end;
  end;
end;// TransferIndexToParentIfNeeded




////////////////////////////////////////////////////////////////////////////////
//
// TABSAOJoin
//
////////////////////////////////////////////////////////////////////////////////


//------------------------------------------------------------------------------
// records are called Equal if all their join attributes are equal
//------------------------------------------------------------------------------
procedure TABSAOJoin.CompareRecords;
var i,j,k:            Integer;
    value1, value2:   TABSVariant;
begin
 FLeftAONull := False;
 FRightAONull := False;
 value1 := TABSVariant.Create;
 value2 := TABSVariant.Create;
 try
   for i := 0 to FFields1.ItemCount - 1 do
    begin
     // get first value
     j := FFields1.Items[i];
     if (not FLeftAO.ResultDataset.Bof) or (not FLeftAO.Eof) then
       GetFieldValue(value1,j)
     else
       value1.SetNull;

     // get second value
     k := FFields2.Items[i];
     if (not FRightAO.ResultDataset.Bof) or (not FRightAO.Eof) then
       GetFieldValue(value2,k)
     else
       value2.SetNull;

     // compare values
     try
      FCompareResult := value1.Compare(value2,True,False,False);
     except
      on e: EABSException do
       begin
         raise EABSException.Create(10328,ErrorLCompareFieldValues,
          [FFieldLinks[j].FieldName,FFieldLinks[k].FieldName,e.Message]);
       end;
      else
       raise;
     end;
     if (FCompareResult <> cmprEqual) then
       break;
    end;
 finally
   value1.Free;
   value2.Free;
 end; // try .. finally
end; // CompareRecords


//------------------------------------------------------------------------------
// go to first record
//------------------------------------------------------------------------------
procedure TABSAOJoin.InternalFirst;
begin
 FLeftAO.First;
 FRightAO.First;
 case FJoinType of
     ajtCross:      FEof := FLeftAO.Eof or FRightAO.Eof;
     ajtFullOuter:  FEof := FLeftAO.Eof and FRightAO.Eof;
     ajtLeftOuter:  FEof := FLeftAO.Eof;
     ajtRightOuter: FEof := FRightAO.Eof;
 end;

 if (not FEof) then
   begin
     FEqualStarted := False;
     FFirstTimeCalled := True;
     FBothNullsStarted := False;
     FLeftAOEmpty := False;
     FRightAOEmpty := False;
     InternalNext;
   end;
 FFirstTimeCalled := False;
end; // First

(*
//------------------------------------------------------------------------------
// go to next record
//------------------------------------------------------------------------------
procedure TABSAOJoin.InternalNext;

var LeftMoved, RightMoved: Boolean;

 function IsJoinConditionValid: Boolean;
  var OldL, OldR: Boolean;
  begin
    Result := True;
    if (FJoinCondition <> nil) then
      begin
        OldL := FLeftAONull;
        OldR := FRightAONull;
        FLeftAONull := False;
        FRightAONull := False;
        Result := TABSExpression(FJoinCondition).GetResult;
        FLeftAONull := OldL;
        FRightAONull := OldR;
      end;
  end;

 procedure GetBookmarkFromRightAO(var Bookmark: Pointer);
 begin
   if (Bookmark <> nil) then
     FRightAO.ResultDataset.FreeBookmark(Bookmark);
   Bookmark := FRightAO.ResultDataset.GetBookmark;
 end;

 procedure GotoBookMarkAtRightAO(Bookmark: Pointer);
 begin
   if (Bookmark <> nil) then
     FRightAO.ResultDataset.GotoBookmark(Bookmark);
 end;

 procedure LowerestAONext;
 begin
  LeftMoved := False;
  RightMoved := False;
  CompareRecords;
  if (FLeftAO.Eof or FRightAO.Eof) then
   begin
    if FLeftAO.Eof then
     begin
      FRightAO.Next;
      RightMoved := True;
     end
    else
     begin
      FLeftAO.Next;
      LeftMoved := True;
     end;
   end
  else
   begin
    if (FCompareResult in [cmprLower, cmprBothNull, cmprLeftNull]) then
     begin
      FLeftAO.Next;
      LeftMoved := True;
      {if (FLeftAO.Eof and (FCompareResult in [cmprLower])) then
        FRightAO.Next;}
     end;
    if (FCompareResult in [cmprGreater, cmprBothNull, cmprRightNull, cmprEqual]) then
     begin
      FRightAO.Next;
      RightMoved := True;
      if (FRightAO.Eof and (FCompareResult in [cmprEqual])) then
       begin
        FLeftAO.Next;
        LeftMoved := True;
       end;
     end;
   end;
  CompareRecords;
 end;//LowerestAONext


 function IsRecordsLinked: Boolean;
 begin
  if (FCompareResult = cmprEqual) and IsJoinConditionValid then
   begin
    if (not FEqualStarted) then
     begin
      GetBookmarkFromRightAO(FRightBeginBookmark);
      FEqualStarted := true;
     end;
    GetBookmarkFromRightAO(FRightEndBookmark);
    Result := True;
   end
  else
   Result := False;
 end;//IsRecordsLinked


 function EqualsRecordsNext: Boolean;
 begin
  if (FEqualStarted and LeftMoved) then
    GotoBookMarkAtRightAO(FRightBeginBookmark);

  CompareRecords;
  Result := IsRecordsLinked;
  if Result then Exit;

  if FEqualStarted then
   begin
    repeat
     CompareRecords;
     // equals ?
     if IsRecordsLinked then Break;

     // equal and Join condition is INVALID
     if (FCompareResult = cmprEqual) then
       begin
         FEqualStarted := False;
         GotoBookMarkAtRightAO(FRightBeginBookmark);
         CompareRecords;
         Result := False;
         Exit;
       end;

     // L > R then Exit
     if (FCompareResult = cmprGreater) then
      begin
       FEqualStarted := False;
       GotoBookMarkAtRightAO(FRightEndBookmark);
       //R.Next;
       CompareRecords;
       Result := False;
       Exit;
      end
     else
     // L.Next and R to First equal Record
     //if (FCompareResult = cmprLower) then
      begin
       //if L.Eof then Exit;
       FLeftAO.Next;
       if FLeftAO.Eof then
        begin
         FEqualStarted := False;
         GotoBookMarkAtRightAO(FRightEndBookmark);
         //R.Next;
         //L.ResultDataset.Last; //+
         CompareRecords;
         Result := False;
         Exit;
        end;
       GotoBookMarkAtRightAO(FRightBeginBookmark);
       Continue;
      end;
     FRightAO.Next;
    until FLeftAO.Eof;
    Result := True;
   end; //FEqualStarted
 end;//EqualsAONext


 procedure DekartNext;
 begin
  if FFirstTimeCalled then
    Exit;
  FRightAO.Next;
  if (FRightAO.Eof) then
   begin
    FLeftAO.Next;
    if (not FLeftAO.Eof) then
     FRightAO.First;
   end; // Eof
 end;//DekartNext

 
 procedure InnerNext;
 begin
  if (not FFirstTimeCalled) then
   begin
    LowerestAONext;
    if Eof then Exit;
    if (FEqualStarted) and (FRightAO.Eof)  then
      begin
       FEqualStarted := False;
       GotoBookMarkAtRightAO(FRightBeginBookmark);
       CompareRecords;
      end;
   end;
  //if (FRightAO.Eof or FLeftAO.Eof) then LowerestAONext;}
  if EqualsRecordsNext then Exit;
  repeat
   LowerestAONext;
   if IsRecordsLinked then Break;
   if (FRightAO.Eof or FLeftAO.Eof) then
     FEof := True;
  until Eof;
 end;//InnerNext


 procedure FullOuterNext;
 begin
  if (not FFirstTimeCalled) and (not FBothNullsStarted) then
    LowerestAONext
  else
    CompareRecords;

  // untested patch begin
  if (LeftMoved and RightMoved) and (FCompareResult = cmprEqual) and (FJoinCondition <> nil) then
    begin
      LeftMoved := False;
      FLeftAO.FResultDataset.Prior;
      FLeftAO.FResultDataset.Next;
    end;
  // untested patch end

  if Eof then Exit;

  if FEqualStarted then
   begin
    if EqualsRecordsNext then Exit;
    LowerestAONext;
   end;

  // Check for both IS NULL
  if (FCompareResult = cmprBothNull) then
   begin
    if not FBothNullsStarted then
     begin
      FBothNullsStarted := True;
      FLeftAONull := False;
      FRightAONull := True;
     end                               
    else
     begin
      FBothNullsStarted := False;
      FLeftAONull := True;
      FRightAONull := False;
     end;
    Exit;
   end;

  if Eof then Exit;

  if IsRecordsLinked then Exit;

  if (FLeftAO.Eof or FRightAO.Eof) then
   begin
    if (FLeftAO.Eof) then
     begin
      FLeftAONull := True;
      FRightAONull := False;
     end
    else
     begin
      FLeftAONull := False;
      FRightAONull := True;
     end;
   end
  else
   begin
    FLeftAONull := (FCompareResult in [cmprRightNull, cmprGreater, cmprEqual]);
    FRightAONull := (FCompareResult in [cmprLeftNull, cmprBothNull, cmprLower]);
   end;
 end;// FullOuterNext


 procedure LeftNext;
 begin
   repeat
     FullOuterNext;
     if (FFirstTimeCalled and FRightAO.Eof) then
       FRightAOEmpty := True;
     FFirstTimeCalled := False;
     if (not FLeftAONull) then Break;
     if (FRightAO.Eof and not FRightAOEmpty) then Continue;
     if FLeftAO.Eof then FEof := True;
   until Eof;
 end;// LeftNext


 procedure RightNext;
 begin
   repeat
     FullOuterNext;
     if (FFirstTimeCalled and FLeftAO.Eof) then
       FLeftAOEmpty := True;
     FFirstTimeCalled := False;
     if (not FRightAONull) then Break;
     if (FLeftAO.Eof and not FLeftAOEmpty) then Continue;
     if FRightAO.Eof then FEof := True;
   until Eof;
 end;// RightNext


begin
    FLeftAONull := False;
    FRightAONull := False;

    case FJoinType of
     ajtCross:      DekartNext;     // ? on condition
     ajtInner:      InnerNext;
     ajtLeftOuter:  LeftNext;
     ajtRightOuter: RightNext;
     ajtFullOuter:  FullOuterNext;
    end;
end; // InternalNext
*)


//------------------------------------------------------------------------------
// go to next record
//------------------------------------------------------------------------------
procedure TABSAOJoin.InternalNext;

var LeftMoved, RightMoved: Boolean;

 function IsJoinConditionValid: Boolean;
  var OldL, OldR: Boolean;
  begin
    Result := True;
    if (FJoinCondition <> nil) then
      begin
        OldL := FLeftAONull;
        OldR := FRightAONull;
        FLeftAONull := False;
        FRightAONull := False;
        Result := TABSExpression(FJoinCondition).GetResult;
        FLeftAONull := OldL;
        FRightAONull := OldR;
      end;
  end;

 procedure GetBookmarkFromRightAO(var Bookmark: TBookmark);
 begin
   if (Bookmark <> nil) then
     FRightAO.ResultDataset.FreeBookmark(Bookmark);
   Bookmark := FRightAO.ResultDataset.GetBookmark;
 end;

 procedure GetBookmarkFromAO(AO: TABSAO; var Bookmark: TBookmark);
 begin
   if (Bookmark <> nil) then
     AO.ResultDataset.FreeBookmark(Bookmark);
   Bookmark := AO.ResultDataset.GetBookmark;
 end;

 procedure GotoBookMarkAtRightAO(Bookmark: TBookmark);
 begin
   if (Bookmark <> nil) then
     FRightAO.ResultDataset.GotoBookmark(Bookmark);
 end;


 procedure GotoBookMarkAtAO(AO: TABSAO; Bookmark: TBookmark);
 begin
   if (Bookmark <> nil) then
     AO.ResultDataset.GotoBookmark(Bookmark)
   else
     AO.ResultDataset.First;
 end;


 procedure LowerestAONext;
 begin
  LeftMoved := False;
  RightMoved := False;
  CompareRecords;
  if (FLeftAO.Eof or FRightAO.Eof) then
   begin
    if FLeftAO.Eof then
     begin
      FRightAO.Next;
      RightMoved := True;
     end
    else
     begin
      FLeftAO.Next;
      LeftMoved := True;
     end;
   end
  else
   begin
    if (FCompareResult in [cmprLower, cmprBothNull, cmprLeftNull]) then
     begin
      FLeftAO.Next;
      LeftMoved := True;
      {if (FLeftAO.Eof and (FCompareResult in [cmprLower])) then
        FRightAO.Next;}
     end;
    if (FCompareResult in [cmprGreater, cmprBothNull, cmprRightNull, cmprEqual]) then
     begin
      FRightAO.Next;
      RightMoved := True;
      if (FRightAO.Eof and (FCompareResult in [cmprEqual])) then
       begin
        FLeftAO.Next;
        LeftMoved := True;
       end;
     end;
   end;
  CompareRecords;
 end;//LowerestAONext


 function IsRecordsLinked: Boolean;
 begin
  if (FCompareResult = cmprEqual) and IsJoinConditionValid then
   begin
    if (not FEqualStarted) then
     begin
      GetBookmarkFromRightAO(FRightBeginBookmark);
      FEqualStarted := true;
     end;
    GetBookmarkFromRightAO(FRightEndBookmark);
    Result := True;
   end
  else
   Result := False;
 end;//IsRecordsLinked


 function EqualsRecordsNext: Boolean;
 begin
  if (FEqualStarted and LeftMoved) then
    GotoBookMarkAtRightAO(FRightBeginBookmark);

  CompareRecords;
  Result := IsRecordsLinked;
  if Result then Exit;

  if FEqualStarted then
   begin
    repeat
     CompareRecords;
     // equals ?
     if IsRecordsLinked then Break;

     // equal and Join condition is INVALID
     if (FCompareResult = cmprEqual) then
       begin
         FEqualStarted := False;
         GotoBookMarkAtRightAO(FRightBeginBookmark);
         CompareRecords;
         Result := False;
         Exit;
       end;

     // L > R then Exit
     if (FCompareResult = cmprGreater) then
      begin
       FEqualStarted := False;
       GotoBookMarkAtRightAO(FRightEndBookmark);
       //R.Next;
       CompareRecords;
       Result := False;
       Exit;
      end
     else
     // L.Next and R to First equal Record
     //if (FCompareResult = cmprLower) then
      begin
       //if L.Eof then Exit;
       FLeftAO.Next;
       if FLeftAO.Eof then
        begin
         FEqualStarted := False;
         GotoBookMarkAtRightAO(FRightEndBookmark);
         //R.Next;
         //L.ResultDataset.Last; //+
         CompareRecords;
         Result := False;
         Exit;
        end;
       GotoBookMarkAtRightAO(FRightBeginBookmark);
       Continue;
      end;
     FRightAO.Next;
    until FLeftAO.Eof;
    Result := True;
   end; //FEqualStarted
 end;//EqualsAONext


 procedure DekartNext;
 begin
  if FFirstTimeCalled then Exit;
  FRightAO.Next;
  if (FRightAO.Eof) then
   begin
    FLeftAO.Next;
    if (not FLeftAO.Eof) then
     FRightAO.First;
   end; // Eof
 end;//DekartNext

 
 procedure InnerNext;
 begin
  if (not FFirstTimeCalled) then
   begin
    LowerestAONext;
    if Eof then Exit;
    if (FEqualStarted) and (FRightAO.Eof)  then
      begin
       FEqualStarted := False;
       GotoBookMarkAtRightAO(FRightBeginBookmark);
       CompareRecords;
      end;
   end;
  //if (FRightAO.Eof or FLeftAO.Eof) then LowerestAONext;}
  if EqualsRecordsNext then Exit;
  repeat
   LowerestAONext;
   if IsRecordsLinked then Break;
   if (FRightAO.Eof or FLeftAO.Eof) then
     FEof := True;
  until Eof;
 end;//InnerNext


 procedure FullOuterNext;
 begin
  if (not FFirstTimeCalled) and (not FBothNullsStarted) then
    LowerestAONext
  else
    CompareRecords;

  // untested patch begin
  if (LeftMoved and RightMoved) and (FCompareResult = cmprEqual) and (FJoinCondition <> nil) then
    begin
      LeftMoved := False;
      FLeftAO.FResultDataset.Prior;
      FLeftAO.FResultDataset.Next;
    end;
  // untested patch end

  if Eof then Exit;

  if FEqualStarted then
   begin
    if EqualsRecordsNext then Exit;
    LowerestAONext;
   end;

  // Check for both IS NULL
  if (FCompareResult = cmprBothNull) then
   begin
    if not FBothNullsStarted then
     begin
      FBothNullsStarted := True;
      FLeftAONull := False;
      FRightAONull := True;
     end                               
    else
     begin
      FBothNullsStarted := False;
      FLeftAONull := True;
      FRightAONull := False;
     end;
    Exit;
   end;

  if Eof then Exit;

  if IsRecordsLinked then Exit;

  if (FLeftAO.Eof or FRightAO.Eof) then
   begin
    if (FLeftAO.Eof) then
     begin
      FLeftAONull := True;
      FRightAONull := False;
     end
    else
     begin
      FLeftAONull := False;
      FRightAONull := True;
     end;
   end
  else
   begin
    FLeftAONull := (FCompareResult in [cmprRightNull, cmprGreater, cmprEqual]);
    FRightAONull := (FCompareResult in [cmprLeftNull, cmprBothNull, cmprLower]);
   end;
 end;// FullOuterNext


 procedure LeftNext;
 begin
   repeat
     FullOuterNext;
     if (FFirstTimeCalled and FRightAO.Eof) then
       FRightAOEmpty := True;
     FFirstTimeCalled := False;
     if (not FLeftAONull) then Break;
     if (FRightAO.Eof and not FRightAOEmpty) then Continue;
     if FLeftAO.Eof then FEof := True;
   until Eof;
 end;// LeftNext


 procedure RightNext;
 begin
   repeat
     FullOuterNext;
     if (FFirstTimeCalled and FLeftAO.Eof) then
       FLeftAOEmpty := True;
     FFirstTimeCalled := False;
     if (not FRightAONull) then Break;
     if (FLeftAO.Eof and not FLeftAOEmpty) then Continue;
     if FRightAO.Eof then FEof := True;
   until Eof;
 end;// RightNext


 procedure Nextus;
 var L,R: TABSAO;
 begin
  if (FJoinType in [ajtLeftOuter, ajtInner]) then
   begin
    L := FLeftAO;
    R := FRightAO;
   end
  else // [ajtRightOuter]
   begin
    L := FRightAO;
    R := FLeftAO;
   end;

  repeat
   if not FFirstTimeCalled then
    begin
     // EOF or <
     if ((FJoinType in [ajtLeftOuter, ajtInner]) and
         (FRightAO.Eof or (FFieldsLink and (FCompareResult in [cmprLower,cmprLeftNull,cmprBothNull]))) or
         (FJoinType = ajtRightOuter) and
         (FLeftAO.Eof or  (FFieldsLink and (FCompareResult in [cmprGreater,cmprRightNull,cmprBothNull])))) then
      begin

       L.Next;
       if (FLeftAO.Eof and (FJoinType = ajtInner)) and
          ((FFieldsLink and (FCompareResult in [cmprLower,cmprLeftNull,cmprBothNull])) or (not FFieldsLink)) then
        begin
         FRightAONull := True;
         FEof := True;
         Break;
        end;

       FLinkCount := 0;
       // Set EOF for LeftJoin or RightJoin
       if ((FJoinType in [ajtLeftOuter, ajtRightOuter]) and L.Eof) then FEof := True;
       // Restore to First Equals
       if (not Eof) then {
        begin
         if FFieldsLink then}
          GotoBookMarkAtAO(R,FRightBeginBookmark);
         {else
          R.First;
        end;}
       if Eof then Break;
      end
     else
      begin
       R.Next;
      end;
    end;
   FFirstTimeCalled := False;
   if (FFieldsLink) then CompareRecords;

   // EOF or <
   if ((FJoinType in [ajtLeftOuter, ajtInner, ajtFullOuter]) and
       (FRightAO.Eof or (FFieldsLink and (FCompareResult in [cmprLower,cmprLeftNull,cmprBothNull]))) or
       (FJoinType = ajtRightOuter) and
       (FLeftAO.Eof or  (FFieldsLink and (FCompareResult in [cmprGreater,cmprRightNull,cmprBothNull])))) then
    begin
     if (FLinkCount <> 0) then Continue;
     if (FJoinType = ajtInner) then
      begin
       if FLeftAO.Eof then
         begin
          FRightAONull := FFieldsLink and (FCompareResult in [cmprLower,cmprLeftNull,cmprBothNull]);
          FEof := True;
          Break;
         end
        else
         Continue;
       end;
     if (FJoinType = ajtFullOuter) then FLeftAONull := True;
     if (FJoinType = ajtLeftOuter) then FRightAONull := True;
     if (FJoinType = ajtRightOuter) then FLeftAONull := True;
     Break;
    end;

   // =
   if ((FFieldsLink and (FCompareResult = cmprEqual) or (not FFieldsLink)) and
       IsJoinConditionValid) then
    begin
     if ((FLinkCount = 0) and FFieldsLink) then GetBookmarkFromAO(R, FRightBeginBookmark);
     Inc(FLinkCount);
     Break;
    end;

   // >
   if (FFieldsLink and (FCompareResult = cmprGreater) and (FJoinType = ajtInner)) then
     GetBookmarkFromAO(R, FRightBeginBookmark);
  until Eof;
 end;


begin
    FLeftAONull := False;
    FRightAONull := False;

    case FJoinType of
     ajtCross:      DekartNext;

     ajtInner,
     ajtLeftOuter,
     ajtRightOuter: Nextus;

     ajtFullOuter:  FullOuterNext;
     //ajtFullOuter:  Nextus;
    end;
end; // InternalNext


//------------------------------------------------------------------------------
// return true if cursor points to the last record
//------------------------------------------------------------------------------
function TABSAOJoin.InternalGetEof: Boolean;
begin
 if (FEof) then
  begin
   result := true;
  end
 else
   result := FLeftAO.Eof and FRightAO.Eof;
end; //


//------------------------------------------------------------------------------
// return number of records
//------------------------------------------------------------------------------
function TABSAOJoin.InternalGetRecordCount: Integer;
begin
 result := 0;
 if (FIsMaterialized) then
  result := FResultDataset.RecordCount
 else
 begin
   if (FDekart) then
    begin
     result := FLeftAO.RecordCount * FRightAO.RecordCount;
    end;
 end;
end; // GetRecordCount


//------------------------------------------------------------------------------
// constructor
//------------------------------------------------------------------------------
constructor TABSAOJoin.Create(
                      LeftChild:  TABSAO;
                      RightChild: TABSAO;
                      JoinType:   TABSJoinType;
                      IsNatural:  Boolean;
                      FieldList1: TABSFields; // join fields
                      FieldList2: TABSFields; // field1 = field2
                      JoinCondition: TObject
                      );
var i,j,k,l1,l2: integer;
    LeftFields,RightFields: string;

 procedure FillIndexNames;
 begin
   if (FFieldLinks[j].AO = FLeftAO) then
    begin
      if (LeftFields = '') then
       LeftFields := FLeftAO.FFieldLinks[FFieldLinks[j].FieldNo].FieldName
      else
       LeftFields := LeftFields + ';' + FLeftAO.FFieldLinks[FFieldLinks[j].FieldNo].FieldName;
    end // LeftAO
   else
    begin
      if (RightFields = '') then
       RightFields := FRightAO.FFieldLinks[FFieldLinks[j].FieldNo].FieldName
      else
       RightFields := RightFields + ';' + FRightAO.FFieldLinks[FFieldLinks[j].FieldNo].FieldName;
    end; // RightAO
 end;


begin
 InternalCreate(LeftChild,RightChild,'','');
 // inner join without any condition on fields => cross join
 if (JoinType = ajtInner) and (JoinCondition = nil) then
  if (FieldList1 <> nil) and (FieldList1.ItemCount+FieldList2.ItemCount = 0) then
   JoinType := ajtCross;

 FJoinType := JoinType;
 FJoinCondition := JoinCondition;
 FInnerJoin := (FJoinType = ajtInner);
 FDekart := (FJoinType = ajtCross);
 FOuterJoin := (FJoinType = ajtLeftOuter) or
               (FJoinType = ajtRightOuter) or
               (FJoinType = ajtFullOuter);
 FRightBeginBookmark := nil;
 FRightEndBookmark := nil;
 l1 := 0;
 l2 := 0;
 if (not FDekart) and (not IsNatural) then
  begin
   l1 := FieldList1.ItemCount;
   l2 := FieldList2.ItemCount;
   if (l1 <> l2) then
     raise EABSException.Create(10330,ErrorLDifferentListsLength,[l1,l2]);
  end;
 FFieldsLink := (FieldList1 <> nil) and (FieldList1.ItemCount <> 0);
 FEof := false;
 FEqualStarted := false;
 FFirstTimeCalled := true;
 FFields1 :=  TABSIntegerArray.Create(0,1,FLeftAO.FieldCount);
 FFields2 := TABSIntegerArray.Create(0,1,FRightAO.FieldCount);
 // creating field links for none-union joins
 FFieldCount := FLeftAO.FieldCount + FRightAO.FieldCount;
 SetLength(FFieldLinks,FFieldCount);
 // linking left child
 j := FLeftAO.FieldCount-1;
 for i := 0 to j do
  begin
   FFieldLinks[i].FieldNo := i;
   FFieldLinks[i].Dataset := nil;
   FFieldLinks[i].AO := FLeftAO;
   FFieldLinks[i].FieldName := FLeftAO.FFieldLinks[i].FieldName;
   FFieldLinks[i].DisplayName := '';
   FFieldLinks[i].FieldType := FLeftAO.FFieldLinks[i].FieldType;
   FFieldLinks[i].FieldSize := FLeftAO.FFieldLinks[i].FieldSize;
   FFieldLinks[i].IsHidden := FLeftAO.FFieldLinks[i].IsHidden;
   // bug-fix in 4.84
//   FFieldLinks[i].IsExpression := FLeftAO.FFieldLinks[i].IsExpression;
//   FFieldLinks[i].IsAggregate := FLeftAO.FFieldLinks[i].IsAggregate;
   FFieldLinks[i].IsExpression := false;
   FFieldLinks[i].IsAggregate := false;
   FFieldLinks[i].BLOBCompressionAlgorithm := FLeftAO.FFieldLinks[i].BLOBCompressionAlgorithm;
   FFieldLinks[i].BLOBCompressionMode := FLeftAO.FFieldLinks[i].BLOBCompressionMode;
   FFieldLinks[i].BLOBBlockSize := FLeftAO.FFieldLinks[i].BLOBBlockSize;
  end;
 inc(j);
 // linking right child
 for i := 0 to FRightAO.FieldCount-1 do
  begin
   FFieldLinks[j+i].FieldNo := i;
   FFieldLinks[j+i].Dataset := nil;
   FFieldLinks[j+i].AO := FRightAO;
   FFieldLinks[j+i].FieldName := FRightAO.FFieldLinks[i].FieldName;
   FFieldLinks[j+i].DisplayName := '';
   FFieldLinks[j+i].FieldType := FRightAO.FFieldLinks[i].FieldType;
   FFieldLinks[j+i].FieldSize := FRightAO.FFieldLinks[i].FieldSize;
   FFieldLinks[j+i].IsHidden := FRightAO.FFieldLinks[i].IsHidden;
   // bug-fix in 4.84
//   FFieldLinks[j+i].IsExpression := FRightAO.FFieldLinks[i].IsExpression;
//   FFieldLinks[j+i].IsAggregate := FRightAO.FFieldLinks[i].IsAggregate;
   FFieldLinks[j+i].IsExpression := false;
   FFieldLinks[j+i].IsAggregate := false;
   FFieldLinks[j+i].BLOBCompressionAlgorithm := FRightAO.FFieldLinks[i].BLOBCompressionAlgorithm;
   FFieldLinks[j+i].BLOBCompressionMode := FRightAO.FFieldLinks[i].BLOBCompressionMode;
   FFieldLinks[j+i].BLOBBlockSize := FRightAO.FFieldLinks[i].BLOBBlockSize;
   if (IsNatural) then
    for k := 0 to j-1 do
     if (AnsiUpperCase(FFieldLinks[j+i].FieldName) =
          AnsiUpperCase(FFieldLinks[k].FieldName)) then
      begin
       FFields1.Append(k);
       FFields2.Append(j+i);
       FFieldLinks[k].IsHidden := false;
       FFieldLinks[j+i].IsHidden := false;
       FLeftAO.FieldExists(FFieldLinks[k].FieldName,'',True);
       FRightAO.FieldExists(FFieldLinks[j+i].FieldName,'',True);
      end; // natural join
  end;
 if (FInnerJoin or FOuterJoin)  then
  begin
   if (not IsNatural) then
    begin
     // creating join fields lists
     for i := 0 to l1-1 do
      begin
       // join fields should not be hidden
       j := FieldExists(
        FieldList1.Items[i].FieldName,
        FieldList1.Items[i].TableName,True,FFields1);
       if (j <> 1) then
         raise EABSException.Create(10331,ErrorLInvalidJoinField,
          [FieldList1.Items[i].TableName,FieldList1.Items[i].FieldName,i,j]);
      end;
     for i := 0 to l2-1 do
      begin
       j := FieldExists(
        FieldList2.Items[i].FieldName,
        FieldList2.Items[i].TableName,True,FFields2);
       if (j <> 1) then
         raise EABSException.Create(10332,ErrorLInvalidJoinField,
          [FieldList2.Items[i].TableName,FieldList2.Items[i].FieldName,i,j]);
      end;
    end; // not natural

   if (FFields1.ItemCount <> FFields2.ItemCount) then
    raise EABSException.Create(10332,ErrorLDifferentListsLength,[FFields1.ItemCount,FFields2.ItemCount]);
   ///if (FFields1.ItemCount <= 0) then
   /// raise EABSException.Create(10333,ErrorLEmptyFieldList,[FFields1.ItemCount]);
   LeftFields := '';
   RightFields := '';
   // filling Left and Right fields
   for i := 0 to FFields1.ItemCount-1 do
    begin
     j := FFields1.Items[i];
     k := FFields2.Items[i];
     if (FFieldLinks[j].AO = FFieldLinks[k].AO) then
      raise EABSException.Create(10334,ErrorLBothJoinAOEqual,[FFieldLinks[j].FieldName,
        FFieldLinks[k].FieldName,j,k])
     else
      if (FFieldLinks[j].AO = FRightAO) then
       begin
        // swap left / right join fields
        FFields1.Items[i] := k;
        FFields2.Items[i] := j;
        j := FFields1.Items[i];
        k := FFields2.Items[i];
       end;
     FillIndexNames;
     j := k;
     FillIndexNames;
    end;
   if (LeftFields <> '') then
     FLeftAO.SetIndex(LeftFields,'','');
   if (RightFields <> '') then
     FRightAO.SetIndex(RightFields,'','');
  end; // inner or outer joins
  SetFieldNames;
  FIsMaterialized := false;
end; // create;


//------------------------------------------------------------------------------
// destructor
//------------------------------------------------------------------------------
destructor TABSAOJoin.Destroy;
begin
 if (FFields1 <> nil) then
   FFields1.Free;
 if (FFields2 <> nil) then
  FFields2.Free;
 if (FJoinCondition <> nil) then
   FJoinCondition.Free;

 if (FRightBeginBookmark <> nil) then
 {$IFDEF D12H}
   SetLength(FRightBeginBookmark, 0);
 {$ELSE}
   FreeMem(FRightBeginBookmark);
 {$ENDIF}
 if (FRightEndBookmark <> nil) then
 {$IFDEF D12H}
   SetLength(FRightEndBookmark, 0);
 {$ELSE}
   FreeMem(FRightEndBookmark);
 {$ENDIF}

 inherited Destroy;
end; // destroy


//------------------------------------------------------------------------------
// Execute
//------------------------------------------------------------------------------
procedure TABSAOJoin.Execute(IsRootAO: Boolean; ParentQueryAO: TABSAO; ParentCursor: TABSCursor);
begin
  if (FJoinCondition <> nil) then
     TABSExpression(FJoinCondition).AssignAO(Self, ParentQueryAO, ParentCursor);
  inherited Execute(IsRootAO,ParentQueryAO,ParentCursor);
end;// Execute


////////////////////////////////////////////////////////////////////////////////
//
// TABSAOUnion
//
////////////////////////////////////////////////////////////////////////////////


//------------------------------------------------------------------------------
// records are called Equal if all their join attributes are equal
//------------------------------------------------------------------------------
procedure TABSAOUnion.CompareRecords;
var i,j,k: integer;
    value1, value2: TABSVariant;
begin
 value1 := TABSVariant.Create;
 value2 := TABSVariant.Create;
 try
   for i := 0 to FFields1.ItemCount - 1 do
    begin
     // get first value
     j := FFields1.Items[i];
     TABSDataset(FLeftAO.FResultDataset).GetFieldValue(value1,j, False);
     // get second value
     k := FFields2.Items[i];
     TABSDataset(FRightAO.FResultDataset).GetFieldValue(value2,k, False);
     // compare values
     try
      FCompareResult := value1.Compare(value2,False,False,False);
     except
      on e: EABSException do
       begin
         raise EABSException.Create(10335,ErrorLCompareFieldValues,
            [FFieldLinks[j].FieldName,FFieldLinks[k].FieldName,e.Message]);
       end;
      else
       raise;
     end;
     if (FCompareResult <> cmprEqual) and (FCompareResult <> cmprBothNull) then
      begin
       break;
      end;
    end;
   // NULL values are equal
   if (FCompareResult = cmprBothNull) then
    FCompareResult := cmprEqual
   else
   if (FCompareResult = cmprLeftNull) then
    FCompareResult := cmprLower
   else
   if (FCompareResult = cmprRightNull) then
    FCompareResult := cmprGreater;
 finally
   value1.Free;
   value2.Free;
 end;
end; // CompareRecords


procedure TABSAOUnion.ShowLeftAO;
var i,k: Integer;
begin
 k := 0;
 for i := 0 to FFieldCount-1 do
  begin
   FFieldLinks[i].Dataset := FLeftAO.FResultDataset;
   FFieldLinks[i].FieldNo := FFields1.Items[k];
   inc(k);
  end;
end; // ShowLeftAO


procedure TABSAOUnion.ShowRightAO;
var i,k: Integer;
begin
 k := 0;
 for i := 0 to FFieldCount-1 do
  begin
   FFieldLinks[i].Dataset := FRightAO.FResultDataset;
   FFieldLinks[i].FieldNo := FFields2.Items[k];
   inc(k);
  end;
end; // ShowRightAO


//------------------------------------------------------------------------------
// first
//------------------------------------------------------------------------------
procedure TABSAOUnion.InternalFirst;
begin
 FLeftAO.First;
 FRightAO.First;
 if ((FUnionType <> autUnion) or (FLeftAO.Eof)) then
  begin
   FFirstTimeCalled := true;
   InternalNext;
  end;
end; // InternalFirst


//------------------------------------------------------------------------------
// next
//------------------------------------------------------------------------------
procedure TABSAOUnion.InternalNext;
begin
 if (Eof) then Exit;
 if (FUnionType = autUnion) then
  begin
   if (FDistinctApplied) then
     begin
       if (FShowLeft and (not FLeftAO.Eof)) then
         FLeftAO.Next
       else
       if (not FShowLeft) and (not FRightAO.Eof) then
         FRightAO.Next;
       if (not FLeftAO.Eof) and (not FRightAO.Eof) then
         begin
           CompareRecords;
           if (FCompareResult = cmprEqual) then
             begin
               while (FCompareResult = cmprEqual) do
                 begin
                   FRightAO.Next;
                   if (not FRightAO.Eof) then
                     CompareRecords
                   else
                     break;
                 end;
             end;
           // 5.11 fix wrong records order 26.11.2006
           // 5.10 code:
            if (FCompareResult = cmprLower) then
             FShowLeft := (not FLeftAO.Eof)
           else
             FShowLeft := (FRightAO.Eof);
           // 5.11 code:
           //FShowLeft := (not FLeftAO.Eof);
           // causes AST test error
           // end fix
         end
       else
         FShowLeft := (not FLeftAO.Eof);
       if (FShowLeft) then
         ShowLeftAO
       else
         ShowRightAO;
     end
   else
     begin
       if (not FShowLeft) then
        FRightAO.Next
       else
        FLeftAO.Next;
       if FShowLeft and (FLeftAO.Eof) and (not FRightAO.Eof) then
        begin
         ShowRightAO;
         FShowLeft := false;
         // switch to the right AO
        end;
     end;
  end
 else
 if (FUnionType = autIntersect) then
  begin
   if (FFirstTimeCalled) then
    begin
     repeat
      CompareRecords;
      if (FCompareResult = cmprLower) then
       FLeftAO.Next;
      if (FCompareResult = cmprGreater) then
        begin
         if (FRightAO.Eof) then
          begin
           FEof := true;
           break;
          end;
         FRightAO.Next;
        end;
     until (FCompareResult = cmprEqual) or Eof; // find equal records
     FFirstTimeCalled := false;
    end // first time called
   else
    begin
     FLeftAO.Next;
     while (not Eof) do
      begin
       CompareRecords;
       if (FCompareResult = cmprEqual) then
        break; // new record found
       if (FCompareResult = cmprLower) then
        FLeftAO.Next;
       if (FCompareResult = cmprGreater) then
        begin
         if (FRightAO.Eof) then
          begin
           FEof := true;
           break;
          end;
         FRightAO.Next;
        end;
      end; // find equal records
    end; // not first time called
  end // intersect
 else
 if (FUnionType = autExcept) then
  begin
   if (FFirstTimeCalled) then
    begin
     repeat
      if (FRightAO.Eof) then
       break;
      CompareRecords;
      if (FCompareResult = cmprEqual) then
       begin
        FLeftAO.Next;
       end;
      if (FCompareResult = cmprLower) then
       begin
        break;
       end; // lower
      if (FCompareResult = cmprGreater) then
       begin
        if (FRightAO.Eof) then
         break;
        FRightAO.Next;
       end; // Greater
     until Eof; // find equal records
     FFirstTimeCalled := false;
    end // first time called
   else
    begin
     // not first time called
     FLeftAO.Next;
     while (not Eof) do
      begin
       if (FRightAO.Eof) then
        break;
       CompareRecords;
       if (FCompareResult = cmprEqual) then
        begin
         FLeftAO.Next;
        end; // Equal
      if (FCompareResult = cmprLower) then
       begin
        break;
       end; // Lower
      if (FCompareResult = cmprGreater) then
       begin
        if (FRightAO.Eof) then
         break;
        FRightAO.Next;
       end; // Greater
      end; // find equal records
    end; // not first time called
  end // except
 else
  raise EABSException.Create(10336,ErrorLInvalidUnionType,[Integer(FUnionType)]);
end; // InternalNext



//------------------------------------------------------------------------------
// Eof
//------------------------------------------------------------------------------
function TABSAOUnion.InternalGetEof: Boolean;
begin
 if (FEof) then
  begin
   result := true;
   Exit;
  end;
 if (FUnionType = autIntersect) and (FRightAO.RecordCount = 0) then
  begin
   result := true;
   Exit;
  end;
 if (FUnionType = autExcept) or (FUnionType = autIntersect) then
  result := FLeftAO.Eof
 else
  result := FEof or (FLeftAO.Eof and FRightAO.Eof);
end; // InternalGetEof


//------------------------------------------------------------------------------
// return recordcount
//------------------------------------------------------------------------------
function TABSAOUnion.InternalGetRecordCount: Integer;
begin
 result := 0;
 if (FIsMaterialized) then
  result := FResultDataset.RecordCount;
end; // InternalGetRecordCount


//------------------------------------------------------------------------------
// constructor
//------------------------------------------------------------------------------
constructor TABSAOUnion.Create(
                      LeftChild:  TABSAO;
                      RightChild: TABSAO;
                      UnionType:   TABSUnionType;
                      ParentQueryAO: TABSAO;
                      ParentCursor: TABSCursor;
                      IsCorresponding:  Boolean = False;
                      bDistinct: Boolean = True;
                      FieldList: TABSFields=nil // corresponding fields
                      );
var LeftIndexFieldNames,RightIndexFieldNames: string;

procedure AddFieldToFieldLists(LeftIndex: integer; RightIndex: integer);
var
  Name: string;
begin
{      if ((TABSTable(FLeftAO.FResultDataset).AdvFieldDefs[LeftIndex].DataType =
          TABSTable(FRightAO.FResultDataset).AdvFieldDefs[RightIndex].DataType) or
          (
            (TABSTable(FLeftAO.FResultDataset).AdvFieldDefs[LeftIndex].DataType in [aftInteger,aftAutoInc]) and
            (TABSTable(FRightAO.FResultDataset).AdvFieldDefs[RightIndex].DataType in [aftInteger,aftAutoInc])
          )) then}
       if (((UnionType = autUnion) and (bDistinct)) or
           (IsConvertableFieldType(TABSTable(FLeftAO.FResultDataset).AdvFieldDefs[LeftIndex].DataType) and
            IsConvertableFieldType(TABSTable(FRightAO.FResultDataset).AdvFieldDefs[RightIndex].DataType))) then
        begin
         inc(FFieldCount);
         SetLength(FFieldLinks,FFieldCount);
         name := FLeftAO.FResultDataset.Fields[LeftIndex].FieldName;
         FFieldLinks[FFieldCount-1].FieldName := name;
         FFieldLinks[FFieldCount-1].DisplayName := name;
         FFieldLinks[FFieldCount-1].FieldType := TABSTable(FLeftAO.FResultDataset).AdvFieldDefs[LeftIndex].DataType;
         FFieldLinks[FFieldCount-1].FieldSize := Max(TABSTable(FLeftAO.FResultDataset).AdvFieldDefs[LeftIndex].Size,
                                                     TABSTable(FRightAO.FResultDataset).AdvFieldDefs[RightIndex].Size);

         FFieldLinks[FFieldCount-1].IsHidden := false;
         FFieldLinks[FFieldCount-1].AO := nil;
         FFieldLinks[FFieldCount-1].Dataset := FLeftAO.FResultDataset;
         FFieldLinks[FFieldCount-1].FieldNo := LeftIndex;
         FFieldLinks[FFieldCount-1].IsExpression := False;
         FFieldLinks[FFieldCount-1].IsAggregate := False;
         FFieldLinks[FFieldCount-1].IsExpression := False;
         FFieldLinks[FFieldCount-1].IsAggregate := False;
         // 5.12 fix blobs union bug
         FFieldLinks[FFieldCount-1].BLOBCompressionAlgorithm := byte(TABSTable(FLeftAO.FResultDataset).AdvFieldDefs[LeftIndex].BlobCompressionAlgorithm);
         FFieldLinks[FFieldCount-1].BLOBCompressionMode := TABSTable(FLeftAO.FResultDataset).AdvFieldDefs[LeftIndex].BLOBCompressionMode;
         FFieldLinks[FFieldCount-1].BLOBBlockSize := TABSTable(FLeftAO.FResultDataset).AdvFieldDefs[LeftIndex].BLOBBlockSize;

         FFields1.Append(LeftIndex);
         FFields2.Append(RightIndex);
         if (LeftIndexFieldNames = '') then
          LeftIndexFieldNames := name
         else
          LeftIndexFieldNames := LeftIndexFieldNames + ';' +  name;
         if (RightIndexFieldNames = '') then
          RightIndexFieldNames := FRightAO.FResultDataset.Fields[RightIndex].FieldName
         else
          RightIndexFieldNames := RightIndexFieldNames + ';' +
            FRightAO.FResultDataset.Fields[RightIndex].FieldName;
        end;
end; // AddFieldToFieldLists


var i,j,k,n,f1,f2: integer;
    name: string;
    bNoFields: Boolean;
begin
 InternalCreate(LeftChild,RightChild,'','');
 FIsMaterialized := False;
 FEof := false;
 FFirstTimeCalled := false;
 FLeftAONull := false;
 FRightAONull := false;
 FShowLeft := true;
 FUnionType := UnionType;
 bNoFields := false;
 if (FieldList = nil) then
  bNoFields := true;

 FLeftAO.Execute(False, ParentQueryAO, ParentCursor);
 FRightAO.Execute(False, ParentQueryAO, ParentCursor);

 LeftIndexFieldNames := '';
 RightIndexFieldNames := '';
 FFields1 :=  TABSIntegerArray.Create(0,1,FLeftAO.FieldCount);
 FFields2 := TABSIntegerArray.Create(0,1,FRightAO.FieldCount);
 if bNoFields then
  begin
   // scanning all fields in FLeftAO
    for i := 0 to FLeftAO.FResultDataset.FieldCount-1 do
    begin
     k := -1;
     if (IsCorresponding) then
      begin
       name := AnsiUpperCase(FLeftAO.FResultDataset.Fields[i].FieldName);
       for j := 0 to FRightAO.FResultDataset.FieldCount-1 do
        begin
         if (name = AnsiUpperCase(FRightAO.FResultDataset.Fields[j].FieldName)) then
          begin
           k := j;
           break;
          end;
        end;
      end // searching for corresponding field
     else
      begin
       if (i < FRightAO.FResultDataset.FieldCount) then
        k := i;
      end; // corresponding fields by number
     if (k >= 0) then
      AddFieldToFieldLists(i,k);
    end; // scanning all fields in FLeftAO
  end // no corresponding fields were specified
 else
  begin
   // scanning FieldList
   n := FieldList.ItemCount;
   if (n <= 0) then
    raise EABSException.Create(10337,ErrorLEmptyFieldList,[n]);
   for i := 0 to n-1 do
    begin
     // store field name
     name := AnsiUpperCase(FieldList.Items[i].FieldName);
     // scanning LeftAO
     f1 := -1;
     for j := 0 to FLeftAO.FResultDataset.FieldCount-1 do
      begin
       if (name = AnsiUpperCase(FLeftAO.FResultDataset.Fields[j].FieldName)) then
        begin
         f1 := j;
         break;
        end;
      end; // scanning LeftAO
     // scanning RightAO
     f2 := -1;
     for j := 0 to FRightAO.FResultDataset.FieldCount-1 do
      begin
       if (name = AnsiUpperCase(FRightAO.FResultDataset.Fields[j].FieldName)) then
        begin
         f2 := j;
         break;
        end;
      end; // scanning RightAO
     if (f1 >= 0) and (f2 >= 0) then
      begin
        AddFieldToFieldLists(f1,f2);
      end; // adding field to the union field list
    end; // scanning FieldList
  end; // corresponding fields were specified
 if (FFields1.ItemCount <> FFields2.ItemCount) then
  raise EABSException.Create(10338,ErrorLDifferentListsLength,
        [FFields1.ItemCount,FFields2.ItemCount]);
 if (FFields1.ItemCount <= 0) then
  raise EABSException.Create(10339,ErrorLEmptyFieldList,[FFields1.ItemCount]);
 if (UnionType <> autUnion) or (bDistinct) then
  begin
   FLeftAO.SetIndex(LeftIndexFieldNames,'','');
   FRightAO.SetIndex(RightIndexFieldNames,'','');
  end;
 if (bDistinct) then
   Self.ApplyDistinct(LeftIndexFieldNames);
end; // Create


//------------------------------------------------------------------------------
// destructor
//------------------------------------------------------------------------------
destructor TABSAOUnion.Destroy;
begin
 if (FFields1 <> nil) then
   FFields1.Free;
 if (FFields2 <> nil) then
   FFields2.Free;
 inherited Destroy;
end; // destroy


////////////////////////////////////////////////////////////////////////////////
//
// TABSAOTableExpr
//
////////////////////////////////////////////////////////////////////////////////





//------------------------------------------------------------------------------
// ReplacePseudonymsInIndexFields
//------------------------------------------------------------------------------
procedure TABSAOTableExpr.ReplacePseudonymsInIndexFields;
var
  i, FieldNo: Integer;
begin
  for i:=0 to FResultIndexFieldsList.Count-1 do
    begin
      if (not FindFieldInFieldLinks(FResultIndexFieldsList[i], FieldNo)) then
        if (FLeftAO.FindFieldInFieldLinks(FResultIndexFieldsList[i], FieldNo)) then
          if (FLeftAO.FFieldLinks[FieldNo].DisplayName <> '') then
            FResultIndexFieldsList[i] := FLeftAO.FFieldLinks[FieldNo].DisplayName;
    end;
end;// ReplacePseudonymsInIndexFields


//------------------------------------------------------------------------------
// AddHiddenIndexFields
//------------------------------------------------------------------------------
procedure TABSAOTableExpr.AddHiddenIndexFields;
var
  i, FieldNo: Integer;
begin
  for i:=0 to FResultIndexFieldsList.Count-1 do
    begin
      if (not FindFieldInFieldLinks(FResultIndexFieldsList[i], FieldNo)) then
        if (FLeftAO.FindFieldInFieldLinks(FResultIndexFieldsList[i], FieldNo)) then
          AddFieldLink(FieldNo,False);
    end;
end;// AddHiddenIndexFields


//------------------------------------------------------------------------------
// first
//------------------------------------------------------------------------------
procedure TABSAOTableExpr.InternalFirst;
begin
 FLeftAO.First;
end; // InternalFirst


//------------------------------------------------------------------------------
// next
//------------------------------------------------------------------------------
procedure TABSAOTableExpr.InternalNext;
begin
 FLeftAO.Next;
end; // InternalNext


//------------------------------------------------------------------------------
// EOF
//------------------------------------------------------------------------------
function TABSAOTableExpr.InternalGetEof: Boolean;
begin
 result := FLeftAO.EOF;
end; // InternalGetEof


//------------------------------------------------------------------------------
// Record count
//------------------------------------------------------------------------------
function TABSAOTableExpr.InternalGetRecordCount: Integer;
begin
 result := FLeftAO.RecordCount;
end; // InternalGetRecordCount


//------------------------------------------------------------------------------
// constructor
//------------------------------------------------------------------------------
constructor TABSAOTableExpr.Create(
                     Child: TABSAO
                    );
var i: integer;
begin
 InternalCreate(Child, nil, '', '');
 FIsMaterialized := false;
 FFieldCount := 0;
 SetLength(FFieldLinks,0);
 if (not FLeftAO.FHasSetResultFields) then
   for i := 0 to FLeftAO.FieldCount-1 do
     AddFieldLink(i)
 else
   for i := 0 to FLeftAO.FResultFieldsOrder.ItemCount-1 do
     AddFieldLink(FLeftAO.FResultFieldsOrder.Items[i]);
end; // Create


//------------------------------------------------------------------------------
// SetTopRowCount
//------------------------------------------------------------------------------
procedure TABSAOTableExpr.SetTopRowCount(FirstRowNo, TopRowCount: Integer);
begin
  inherited SetTopRowCount(FirstRowNo, TopRowCount);
  if (FTopRowCount > -1) then
    if (FLeftAO <> nil) then
      FLeftAO.SetTopRowCount(FirstRowNo, TopRowCount);
end;// SetTopRowCount


//------------------------------------------------------------------------------
// Execute
//------------------------------------------------------------------------------
procedure TABSAOTableExpr.Execute(IsRootAO: Boolean; ParentQueryAO: TABSAO; ParentCursor: TABSCursor);
begin
  if (FLeftAO is TABSAOTable) then
    if (TABSAOTable(FLeftAO).TransferIndexToParentIfNeeded(FResultIndexFieldsList,FResultIndexAscDescFieldsList,FResultIndexCaseInsFieldsList)) then
      begin
        ReplacePseudonymsInIndexFields;
        AddHiddenIndexFields;
      end;
  inherited Execute(IsRootAO,ParentQueryAO,ParentCursor);
end;// Execute


////////////////////////////////////////////////////////////////////////////////
//
// TABSAOGroupBy
//
////////////////////////////////////////////////////////////////////////////////


//------------------------------------------------------------------------------
// records are called Equal if all their join attributes are equal
//------------------------------------------------------------------------------
procedure TABSAOGroupBy.CompareRecords;
var i,j,k: integer;
    value1, value2: TABSVariant;
begin
 value1 := TABSVariant.Create;
 value2 := TABSVariant.Create;
 try
   for i := 0 to FFields.ItemCount - 1 do
    begin
     // get first value
     j := FFields.Items[i];
     FLeftAO.GetFieldValue(value1,j, False);

     // get second value
     k := FLeftAo.FFieldLinks[j].FieldNo;
     TABSDataset(FTempDataset).GetFieldValue(value2,k, FLeftAo.FHasSetResultFields);

     // compare values
     try
       FCompareResult := value1.Compare(value2,False,False,False);
     except
      on e: EABSException do
       begin
         raise EABSException.Create(10340,ErrorLCompareFieldValues,
            [FFieldLinks[j].FieldName,FFieldLinks[k].FieldName,e.Message]);
       end;
      else
       raise;
     end;

     if (FCompareResult <> cmprEqual) and (FCompareResult <> cmprBothNull) then
       break;
    end;
   // NULL values are equal
   if (FCompareResult = cmprBothNull) then
    FCompareResult := cmprEqual
   else
   if (FCompareResult = cmprLeftNull) then
    FCompareResult := cmprLower
   else
   if (FCompareResult = cmprRightNull) then
    FCompareResult := cmprGreater;
 finally
   value1.Free;
   value2.Free;
 end;
end; // CompareRecords


//------------------------------------------------------------------------------
// first
//------------------------------------------------------------------------------
procedure TABSAOGroupBy.InternalFirst;
begin
 FEOF := false;
 FLeftAO.First;

 if (not FAllFields) then
  begin
   // copy filter to temp dataset
   if (FLeftAO.FIsAOTable) and (FLeftAO.FFilterExpr <> nil) then
     TABSTable(FTempDataset).SetSQLFilter(FLeftAO.FFilterExpr, nil, nil);
   FTempDataset.First;
   FEOF := FTempDataset.Eof;
  end;
 FFirstTimeCalled := true;
 InternalNext;
end; // InternalFirst


//------------------------------------------------------------------------------
// next
//------------------------------------------------------------------------------
procedure TABSAOGroupBy.InternalNext;

 procedure Init;
 var i: integer;
 begin
  if (FFilterExpr <> nil) then
    TABSExpression(FFilterExpr).Init;
  if (not FExpressionsExists) then Exit;
  for i := 0 to FFieldCount-1 do
   if (FFieldLinks[i].IsAggregate) then
    begin
     TABSExpression(FFieldLinks[i].Expr).Init;
    end;
 end; // initialize expressions

 procedure Accumulate;
 var i: integer;
 begin
  if (FFilterExpr <> nil) then
    TABSExpression(FFilterExpr).Accumulate;
  if (not FExpressionsExists) then Exit;
  for i := 0 to FFieldCount-1 do
   if (FFieldLinks[i].IsAggregate) then
    begin
     //TABSExpression(FFieldLinks[i].Expr).Accumulate(self);
     TABSExpression(FFieldLinks[i].Expr).Accumulate;
    end;
 end; // accumulate aggregate expressions

 procedure Iterate(FirstTimeCalled: Boolean);
 begin
   if (not FirstTimeCalled) then
     begin
       if (FLeftAO.FIsAOTable) and (FLeftAO.FFilterExpr <> nil) then
         TABSTable(FLeftAO.ResultDataset).SetSQLFilter(FLeftAO.FFilterExpr, nil, nil);
       FLeftAO.Next;
       if (FLeftAO.FIsAOTable) and (FLeftAO.FFilterExpr <> nil) then
         TABSTable(FTempDataset).SetSQLFilter(FLeftAO.FFilterExpr, nil, nil);
       FTempDataset.Next;
       Init;
       if (FLeftAO.EOF) then
        begin
         FEOF := true;
         Exit;
        end;
     end;
   repeat
    CompareRecords;
    Accumulate;
    if (FCompareResult = cmprEqual) then
     begin
       if (FLeftAO.FIsAOTable) and (FLeftAO.FFilterExpr <> nil) then
         TABSTable(FLeftAO.ResultDataset).SetSQLFilter(FLeftAO.FFilterExpr, nil, nil);
       FLeftAO.Next;
       if (FLeftAO.FIsAOTable) and (FLeftAO.FFilterExpr <> nil) then
         TABSTable(FTempDataset).SetSQLFilter(FLeftAO.FFilterExpr, nil, nil);
       FTempDataset.Next;
     end;
   until (FLeftAO.EOF) or (FCompareResult <> cmprEqual);
 end;

// InternalNext
begin
 if (FAllFields) then
  begin
   if (not FFirstTimeCalled) then
    begin
     FEOF := true;
     Exit;
    end;
   FFirstTimeCalled := false;
   if (IsCountAll) then
     DoCountAll
   else
     begin
       // initialize expression
       Init;
       while not (FLeftAO.FResultDataset.EOF) do
        begin
         Accumulate;
         FLeftAO.Next;
        end;
       FLeftAO.First;
     end;
   Exit;
  end;

 // not AllFields
 if (FFirstTimeCalled) then
  begin
   FFirstTimeCalled := false;
   Init;
   if (FLeftAO.EOF) then
    begin
     if (not FExpressionsExists) then
      FEOF := true;
     Exit;
    end;
   // go to next record
   FTempDataset.Next;
   if (FTempDataset.Eof) then
    begin
     // calculate expressions
     Accumulate;
     // group finished - there is only 1 record in source dataset
     Exit;
    end;
   Iterate(True);
  end // First time called
 else
  begin
   // not first time called
   // move to next group
   Iterate(False);
  end; // not first time called
end; // InternalNext


//------------------------------------------------------------------------------
// EOF
//------------------------------------------------------------------------------
function TABSAOGroupBy.InternalGetEof: Boolean;
begin
 result := FEOF;
end; // InternalGetEof


//------------------------------------------------------------------------------
// Record count
//------------------------------------------------------------------------------
function TABSAOGroupBy.InternalGetRecordCount: Integer;
begin
 result := FLeftAO.RecordCount;
end; // InternalGetRecordCount


//------------------------------------------------------------------------------
// Is "select Count(*) from ..."
//------------------------------------------------------------------------------
function TABSAOGroupBy.IsCountAll: Boolean;
var
  i: Integer;
begin
  Result := FAllFields;
  if (Result) then
    begin
      Result := False;
      for i := 0 to FFieldCount-1 do
       if (FFieldLinks[i].IsAggregate) then
        begin
         if (TABSExpression(FFieldLinks[i].Expr).IsAggregatedCountAll) then
           Result := True
         else
           begin
             Result := False;
             break;
           end;
        end;
    end;
end;// IsCountAll


//------------------------------------------------------------------------------
// DoCountAll
//------------------------------------------------------------------------------
procedure TABSAOGroupBy.DoCountAll;
var
  i: Integer;
begin
  for i := 0 to FFieldCount-1 do
   if (FFieldLinks[i].IsAggregate) then
     if (TABSExpression(FFieldLinks[i].Expr).IsAggregatedCountAll) then
       begin
         TABSExpression(FFieldLinks[i].Expr).SetCountAll(RecordCount);
         break;
       end;
end;// DoCountAll


//------------------------------------------------------------------------------
// sets projection for TABSAOTable
//------------------------------------------------------------------------------
procedure TABSAOGroupBy.SetResultFields(var FieldRefs: array of TABSSelectListItem;
          bDistinct: Boolean);
var i,j,k,x:      integer;
    fname,tname:  string;
    fno:          TABSIntegerArray;
begin
 j := Length(FieldRefs);
 if (j <= 0) then
  begin
   inherited SetResultFields(FieldRefs,False);
   Exit;
  end;
 inherited SetResultFields(FieldRefs,False);
 // check for invalid fields in select list
 for i := 0 to FFieldCount-1 do
  begin
   if (FFieldLinks[i].IsExpression and not FFieldLinks[i].IsHidden) then
     if (TABSExpression(FFieldLinks[i].Expr).CanBeUsedInGroupBy(GroupByFields)) then
       continue
     else
       raise EABSException.Create(20269,ErrorAExpressionCouldNotBeUsedInGroupBy,[FFieldLinks[i].FieldName,FFieldLinks[i].DisplayName]);
  end;
 fno := TABSIntegerArray.Create(0,1,1);
 for i := 0 to j-1 do
  begin
   if (FieldRefs[i].IsExpression) then
       continue;
   fname := FieldRefs[i].FieldName;
   tname := FieldRefs[i].TableName;
   fno.SetSize(0);
   if (FieldExists(fname,tname,False,fno) <= 0) then
    begin
     fno.Free;
     raise EABSException.Create(10341,ErrorLCannotFindFieldInTable,[fname,tname]);
    end;
   for k := 0 to fno.ItemCount-1 do
    begin
     x := fno.Items[k];
     if (not FFields.IsValueExists(x)) then
      begin
       fno.Free;
       raise EABSException.Create(10342,ErrorLFieldDoesNotIncludedInGroupByList,
        [tname,fname,x,FFieldLinks[x].FieldName]);
      end;
    end;
  end;
 fno.Free;
end; // SetResultFields


//------------------------------------------------------------------------------
// constructor
//------------------------------------------------------------------------------
constructor TABSAOGroupBy.Create(
                     Child: TABSAO;
                     FieldList: TABSFields;
                     var FieldRefs: array of TABSSelectListItem);
var i,j,k: integer;
    GroupByField: string;
    PseudonymFound: Boolean;
begin
 InternalCreate(Child);
 GroupByFields := '';
 try
   FAllFields := FieldList = nil;
   FTempDataset := nil;
   if (not FAllFields) then
    if (FieldList.ItemCount = 0) then
     FAllFields := true;
  // FLeftAO.Execute;
   FEOF := false;
   FIsAOGroupBy := true;

   FGroupFinished := False;
   FFirstTimeCalled := false;
   FIsMaterialized := false;
   FFields := TABSIntegerArray.Create(0,1,FLeftAO.FieldCount);
   try
     FFieldCount := FLeftAO.FieldCount;
     SetLength(FFieldLinks,FFieldCount);
     for i := 0 to FFieldCount-1 do
      begin
       FFieldLinks[i].AO := FLeftAO;
       FFieldLinks[i].Dataset := nil;
       FFieldLinks[i].FieldNo := i;
       FFieldLinks[i].IsHidden := True;
       FFieldLinks[i].FieldName := FLeftAO.FFieldLinks[i].FieldName;
       FFieldLinks[i].FieldType := FLeftAO.FFieldLinks[i].FieldType;
       FFieldLinks[i].FieldSize := FLeftAO.FFieldLinks[i].FieldSize;
//       FFieldLinks[i].IsExpression := FLeftAO.FFieldLinks[i].IsExpression;
//       FFieldLinks[i].IsAggregate := FLeftAO.FFieldLinks[i].IsAggregate;
       FFieldLinks[i].IsExpression := False;
       FFieldLinks[i].IsAggregate := False;
       FFieldLinks[i].BLOBCompressionAlgorithm := FLeftAO.FFieldLinks[i].BLOBCompressionAlgorithm;
       FFieldLinks[i].BLOBCompressionMode := FLeftAO.FFieldLinks[i].BLOBCompressionMode;
       FFieldLinks[i].BLOBBlockSize := FLeftAO.FFieldLinks[i].BLOBBlockSize;
      end;
     if (not FAllFields) then
      begin
       for i := 0 to FieldList.ItemCount-1 do
        begin
         // unhide Group BY field in children and move it to FFields array
         j := FieldExists(
               FieldList.Items[i].FieldName,
               FieldList.Items[i].TableName,True,FFields);
          if (j = 0) then
            begin
              // find pseudonym
              PseudonymFound := False;
              for j := 0 to Length(FieldRefs)-1 do
                if (AnsiUpperCase(FieldRefs[j].Pseudonym) = AnsiUpperCase(FieldList.Items[i].FieldName)) then
                    begin
                     PseudonymFound := True;
                     if (FieldRefs[j].IsExpression) then
                       if (TABSExpression(FieldRefs[j].ValueExpr).IsAggregated) then
                         PseudonymFound := False;
                      break;
                    end;
              if (PseudonymFound) then
                j := FieldExists(FieldRefs[j].FieldName, FieldRefs[j].TableName,True,FFields)
              else
                j := 0;
            end;
          if (j > 0) then
            begin
              k := FFields.Items[FFields.ItemCount-1];
              GroupByField := FFieldLinks[k].FieldName;
            end
          else
            raise EABSException.Create(20245, ErrorAInvalidGroupByField,
                  [FieldList.Items[i].TableName,FieldList.Items[i].FieldName,i,j]);
         if (GroupByFields = '') then
          GroupByFields := GroupByField
         else
          GroupByFields := GroupByFields + ';' + GroupByField;
        end;
       FLeftAO.SetIndex(GroupByFields,'','');
      end; // not all fields
   except
    FFields.Free;
    FFields := nil;
    raise;
   end;
  except
   FResultDataset.Free;
   FResultDataset := nil;
   FResultFieldsOrder.Free;
   FResultFieldsOrder := nil;
   raise;
  end;
end; // Create


//------------------------------------------------------------------------------
// destructor
//------------------------------------------------------------------------------
destructor TABSAOGroupBy.Destroy;
begin
 if (FFields <> nil) then
  FFields.Free;
 if (not FAllFields) then
  if (FTempDataset <> nil) then
   FTempDataset.Free;
 inherited Destroy;
end; // destroy



////////////////////////////////////////////////////////////////////////////////
//
// TABSAOSQLTopRowCount
//
////////////////////////////////////////////////////////////////////////////////


//------------------------------------------------------------------------------
// go to first record
//------------------------------------------------------------------------------
procedure TABSAOSQLTopRowCount.InternalFirst;
begin
  FLeftAO.First;
end;// InternalFirst


//------------------------------------------------------------------------------
// go to next record
//------------------------------------------------------------------------------
procedure TABSAOSQLTopRowCount.InternalNext;
begin
  FLeftAO.Next;
end;// InternalNext


//------------------------------------------------------------------------------
// return true if EOF
//------------------------------------------------------------------------------
function TABSAOSQLTopRowCount.InternalGetEof: Boolean;
begin
  Result := FLeftAO.GetEof;
end;// InternalGetEof


//------------------------------------------------------------------------------
// return record count
//------------------------------------------------------------------------------
function TABSAOSQLTopRowCount.InternalGetRecordCount: Integer;
begin
  Result := FLeftAO.GetRecordCount;
end;// InternalGetRecordCount


//------------------------------------------------------------------------------
// Create
//------------------------------------------------------------------------------
constructor TABSAOSQLTopRowCount.Create(
                   Child: TABSAO
                  );
var i: integer;
begin
 InternalCreate(Child);
 FIsMaterialized := false;
 FFieldCount := 0;
 // 5.11 fix wrong fields order
 if (not FLeftAO.FHasSetResultFields) then
   for i := 0 to FLeftAO.FieldCount-1 do
     AddFieldLink(i)
 else
   for i := 0 to FLeftAO.FResultFieldsOrder.ItemCount-1 do
     AddFieldLink(FLeftAO.FResultFieldsOrder.Items[i]);

end;// Create


//------------------------------------------------------------------------------
// SetTopRowCount
//------------------------------------------------------------------------------
procedure TABSAOSQLTopRowCount.SetTopRowCount(FirstRowNo, TopRowCount: Integer);
begin
  inherited SetTopRowCount(FirstRowNo, TopRowCount);
  if (FLeftAO <> nil) then
    FLeftAO.SetTopRowCount(FirstRowNo, TopRowCount);
end;// SetTopRowCount


//------------------------------------------------------------------------------
// convert FieldNames, DescFields, CaseInsFields
// to FieldList, AscDescList, CaseInsList
//------------------------------------------------------------------------------
procedure ConvertIndexFieldNamesToLists(
                FieldNames, DescFields, CaseInsFields:  string;
                FieldList, AscDescList, CaseInsList:    TStringList
                );
var sl:   TStringList;
    i,j:  Integer;
begin
  sl := TStringList.Create;
  try
  FillFieldNames(FieldList,FieldNames);
  FillFieldNames(sl,DescFields);
  for i := 0 to FieldList.Count-1 do
   begin
     j := sl.IndexOf(AnsiUpperCase(FieldList[i]));
     if (j >= 0) then
      AscDescList.Add(ABS_DESC)
     else
      AscDescList.Add(ABS_ASC);
   end;
  sl.Clear;
  FillFieldNames(sl,CaseInsFields);
  for i := 0 to FieldList.Count-1 do
   begin
     j := sl.IndexOf(AnsiUpperCase(FieldList[i]));
     if (j >= 0) then
      CaseInsList.Add(ABS_NO_CASE)
     else
      CaseInsList.Add(ABS_CASE);
   end;
  finally
    sl.Free;
  end;
end; // ConvertIndexFieldNames


//------------------------------------------------------------------------------
// convert FieldList, AscDescList, CaseInsList
// to FieldNames, DescFields, CaseInsFields
//------------------------------------------------------------------------------
procedure ConvertListsToIndexFieldNames(
                var FieldNames:     string;
                var DescNames:     string;
                var CaseInsNames:  string;
                FieldList, AscDescList, CaseInsList: TStringList
                );
var i: Integer;
begin
  FieldNames := '';
  DescNames := '';
  CaseInsNames := '';
  for i := 0 to FieldList.Count - 1 do
   begin
    if (FieldNames = '') then
      FieldNames := FieldList[i]
    else
      FieldNames := FieldNames + ';' + FieldList[i];
    if (AscDescList[i] = ABS_DESC) then
     begin
       if (DescNames = '') then
        DescNames := FieldList[i]
       else
        DescNames := DescNames + ';' + FieldList[i];
     end;
    if (CaseInsList[i] = ABS_NO_CASE) then
     begin
       if (CaseInsNames = '') then
        CaseInsNames := FieldList[i]
       else
        CaseInsNames := CaseInsNames + ';' + FieldList[i];
     end;
   end;
end; // ConvertListsToIndexFieldNames

end.
