unit ABSSQLProcessor;

{$I ABSVer.inc}

interface

uses Classes, SysUtils, Windows, DB,

     {$IFDEF DEBUG_LOG}
     ABSDebug,
     {$ENDIF}

{$IFNDEF D6H}
     ABSD4Routines,
{$ENDIF}

     ABSLexer,
     ABSBase,
     ABSRelationalAlgebra,
     ABSTypes,
     ABSExpressions,
     ABSVariant,
     ABSCompression,
     ABSConst,
     ABSConverts,
     ABSExcept;

 type
  TABSSQLUnion = class;


////////////////////////////////////////////////////////////////////////////////
//
// TABSTableReference
//
////////////////////////////////////////////////////////////////////////////////

  // reference to the "table" in FROM clause
  TABSTableReference = class (TObject)
   public
    TableType:        TABSTableType;      // Table | JoinedTable | SubQuery
    DatabaseName:     string;             // database1
    TableName:        string;             // table1
    Pseudonym:        string;             // table1 as t1
    Password:         string;             // table1 PASSWORD 'password'
    InMemory:         Boolean;            // [MEMORY]
    NaturalJoin:      Boolean;            // Natural join?
    JoinType:         TABSJoinType;       // inner | left | ...
    LeftTable:        TABSTableReference; // left table in join
    RightTable:       TABSTableReference; // right table in join
    UsingFields:      TStringList;        // join column list
    SearchCondition:  TABSExpression;     // ON '(t1.Field1 = t2.Field2)'
    SubQuery:         TABSSQLUnion;       // subquery
    ExternalDBToFree: TObject;

    // creates
    constructor Create;
    // destroys
    destructor Destroy; override;
    // makes join (left and right node-table)
    procedure MakeJoin(RightNode: TABSTableReference; JType: TABSJoinType;
                     IsNatural: Boolean; Fields: TStringList;
                     OnCondition: TABSExpression);
    // makes subquery
    procedure MakeSubQuery(Lexer: TABSLexer; Query: TDataSet);
  end;//TABSTableReference


////////////////////////////////////////////////////////////////////////////////
//
// TABSSQLCommand
// base class for TABSSQLSelect, TABSSQLUpdate, ...
//
////////////////////////////////////////////////////////////////////////////////

   // base class for TABSSQLSelect, TABSSQLUpdate, ...
  TABSSQLCommand = class (TObject)
   protected
    LLex:         TABSLexer;   // lexer with expression to parse
    Token:        TToken;      // current token
    LQuery:       TDataSet;
    RowsAffected: Integer;
    IntoMemory:   Boolean;     // into memory table?
   private
    // parses list of columns (without table name): field1, field2, ...
    procedure ParseColumnList(var Fields: TStringList);
    // parses list of fields:  table.field1, field2, ..
    procedure ParseFieldList(var Fields: TABSFields);

    // get current token
    function GetCurrentToken: Boolean;
    // get next token
    function GetNextToken: Boolean; overload;
    // get token and lokks at next token with check for token type restrictions
    function GetNextToken(PermittedTypes: TTokenTypes;
                          NativeErrorCode: integer;
                          ErrorText: string): Boolean; overload;

   protected
    procedure ParseTableNameToken(var DatabaseFileName, TableName, TableAlias, Password: string; var InMemory: Boolean);
    procedure CreateDbAndTableComponents(
                          Query: TDataSet;
                          DatabaseFileName, TableName, Password: string;
                          InMemory: Boolean;
                          var Table: TDataset;
                          var DB: TObject);
    procedure FreeDbAndTableComponents(Table: TDataset; DB: TObject);

   public
    // creates object
    constructor Create(Lexer: TABSLexer; Query: TDataSet);
    // parses query
    procedure Parse; virtual; abstract;
    // executes query
    procedure ExecSQL(
                      query: TDataset;
                      IsRoot,
                      RequestLive: boolean;
                      var ReadOnly: boolean;
                      ParentQueryAO: TABSAO = nil;
                      ParentCursor: TABSCursor = nil
                     ); virtual; abstract;
    // get result cursor
    function GetResultCursor: TABSCursor; virtual;
    // gets result dataset
    function GetResultDataset: TDataset; virtual;
    // check pseudonym
    function CheckPseudonym(var Pseudonym: string): Boolean;
  end;//TABSSQLCommand



////////////////////////////////////////////////////////////////////////////////
//
// TABSSQLCursorCommand
// base class for SQL command with cursor
//
////////////////////////////////////////////////////////////////////////////////


 TABSSQLCursorCommand = class (TABSSQLCommand)
  protected
   ResultDataset:         TDataset;  // query component
   IntoDatabase:          string;    // select into <database>.<table>
   IntoTable:             string;    // select into <database>.<table>
   IntoImmediate:         Boolean;   // select into direct memory table?
   OrderBySpecs:          array of TABSSortSpecification;
   OrderBySpecsCount:     integer;

   function OpenAndLockTablesBeforeSelect: Boolean;
   function UnlockTablesAfterSelect: Boolean;

   // <sort key> [ <collate clause> ] [ <ordering specification> ]
   function ParseSortSpecification: Boolean;
   // ORDER BY
   function ParseOrderByClause: Boolean;
   // applies Order By clause
   procedure ApplyOrderBy(AO: TABSAO);

  public
   RootAO:                TABSAO;   // top level AO

   // creates object
   constructor Create(Lexer: TABSLexer; Query: TDataSet);
   // destroys object
   destructor Destroy; override;
   // executes query
   //procedure ExecSQL(query: TDataset; IsRoot: Boolean = False); override;
   procedure ExecSQL(
                      query: TDataset;
                      IsRoot,
                      RequestLive: boolean;
                      var ReadOnly: boolean;
                      ParentQueryAO: TABSAO = nil;
                      ParentCursor: TABSCursor = nil
                    ); override;
   // check if correlated subquery apllied
   function AllConditionsApplied: Boolean; virtual; abstract;
   // builds AO tree
   function BuildAOTree(query: TDataset; RequestLive: Boolean; ParentQueryAO: TABSAO;
    ParentCursor: TABSCursor; AllowNotAppliedConditions: Boolean): TABSAO; virtual; abstract;

   // get result cursor
   function GetResultCursor: TABSCursor; override;
   // get result dataset
   function GetResultDataset: TDataset; override;
 end;//TABSSQLCursorCommand



////////////////////////////////////////////////////////////////////////////////
//
// TABSSQLProcessor
//
////////////////////////////////////////////////////////////////////////////////


  TABSSQLProcessor = class (TObject)
   private
    ABSQuery:       TDataSet;
    FReadOnly:      Boolean;
    FRequestLive:   Boolean;
    FInMemory:      Boolean;
    FRowsAffected:  TABSRecordNo;
    FLex:           TABSLexer;

    FSqlText:       string;
    FSQLParams:     TABSSQLParams;
    FCursor:        TABSCursor;

    Queries:        array of TABSSQLCommand; // objects fullfilled by parser
   private

    // parse SQL script and fullfill SQL command objects
    function ParseNextCommand(Lexer: TABSLexer): Boolean;

    // add SELECT query object
    procedure AddSelectQuery(Lexer: TABSLexer);
    // add INSERT query object
    procedure AddInsertQuery(Lexer: TABSLexer);
    // add UPDATE query object
    procedure AddUpdateQuery(Lexer: TABSLexer);
    // add DELETE query object
    procedure AddDeleteQuery(Lexer: TABSLexer);
    // DDL:
    // add TRUNCATE TABLE
    procedure AddTruncateTableQuery(Lexer: TABSLexer);
    // add CREATE TABLE query object
    procedure AddCreateTableQuery(Lexer: TABSLexer);
    // add DROP TABLE query object
    procedure AddDropTableQuery(Lexer: TABSLexer);
    // add ALTER TABLE
    procedure AddAlterTableQuery(Lexer: TABSLexer);
    // add RENAME TABLE
    procedure AddRenameTableQuery(Lexer: TABSLexer);
    // add CREATE INDEX
    procedure AddCreateIndex(Lexer: TABSLexer);
    // add DROP INDEX
    procedure AddDropIndexQuery(Lexer: TABSLexer);
    // add START TRANSACTION
    procedure AddStartTransaction(Lexer: TABSLexer);
    // add COMMIT
    procedure AddCommit(Lexer: TABSLexer);
    // add ROLLBACK
    procedure AddRollback(Lexer: TABSLexer);

    procedure ClearQueries;

   public
    constructor Create(Query: TDataSet);
    destructor Destroy; override;

    function OpenQuery: TABSCursor;
    procedure ExecuteQuery;

    procedure PrepareStatement(SQLText: PChar);

   public
    property ReadOnly: Boolean read FReadOnly write FReadOnly;
    property RequestLive: Boolean read FRequestLive write FRequestLive;
    property InMemory: Boolean read FInMemory write FInMemory;
    property RowsAffected: TABSRecordNo read FRowsAffected;
    property SQLParams: TABSSQLParams read FSQLParams;
  end; // TABSSQLProcessor



////////////////////////////////////////////////////////////////////////////////
//
// TABSSQLSelect
//
////////////////////////////////////////////////////////////////////////////////


 TABSSQLSelect = class (TABSSQLCursorCommand)
  protected
   Distinct:              Boolean; // ALL | DISTINCT
   TopRowCount:           Integer; // TOP (-1 | n)
   FirstRowNo:            Integer; // TOP row count [, first row]
   AllFields:             Boolean; // Select *
   SelectList:            array of TABSSelectListItem; // fields list
   SelectListCount:       integer; // count of array elements
   FromTables:            array of TABSTableReference; // From clause
   FromTablesCount:       integer; // count of array elements
   SearchCondition:       TABSExpression;  // WHERE clause
   GroupByFields:         TABSFields; // GROUP BY field1, f2, ...
   HavingCondition:       TABSExpression;  // HAVING clause
   FSubqueryHasNotAppliedConditions: Boolean;

   // parse SELECT token
   function ParseSelectToken: Boolean;
   // DISTINCT | ALL ?
   function ParseSetQuantifier: Boolean;
   // TOP n ?
   function ParseTopOperator: Boolean;
   function DuplicatedFieldExists(const TableName, FieldName: string): Boolean;
   function FieldExists(const TableName, FieldName: string): Boolean;
   function GetFieldNameForDuplicatedField(const TableName, FieldName: string): string;
   // table1.* | Table1.Field1 | Fields1 AS F1
   function ParseSelectSubList: Boolean;
   // * | <select sublist>
   function ParseSelectList: Boolean;
   // INTO <target>
   function ParseInto: Boolean;
   // ON <join condition>
   function ParseJoinCondition(var SearchCondition: TABSExpression): Boolean;
   // USING <join columns>
   function ParseNamedColumnsJoin(var Fields: TStringList): Boolean;
   // CROSS JOIN | INNER JOIN | ...
   function ParseJoin(var tblRef: TABSTableReference): Boolean;
   // Subquery: SELECT FROM (SELECT ...)
   function ParseFromSubquery(tblRef: TABSTableReference): Boolean;
   // <table name> [ [ AS ] <correlation name> ...
   function ParseTableReference(tblRef: TABSTableReference=nil): Boolean;
   // FROM ...
   function ParseFromClause: Boolean;
   // WHERE ...
   function ParseWhereClause: Boolean;
   // GROUP BY ...
   function ParseGroupByClause: Boolean;
   // HAVING ...
   function ParseHavingClause: Boolean;

   // gets default database name
   function GetDefaultDatabaseName: string;
   // gets DisableTempFiles value
   function GetDisableTempFiles: boolean;
   // creates and adjusts table AO
   function CreateTableAO(var TableRef: TABSTableReference): TABSAO;
   // creates and adjusts joined table AO
   function CreateJoinedTableAO(var TableRef: TABSTableReference;
                       ParentQueryAO: TABSAO; ParentCursor: TABSCursor;
                       AllowNotAppliedConditions: Boolean): TABSAO;
   // creates and adjusts subquery AO
   function CreateSubQueryAO(var TableRef: TABSTableReference; RequestLive: Boolean;
                             ParentQueryAO: TABSAO; ParentCursor: TABSCursor;
                             AllowNotAppliedConditions: Boolean): TABSAO;
   // creates and adjusts AO
   function CreateAO(var TableRef: TABSTableReference; RequestLive: Boolean;
          ParentQueryAO: TABSAO;
          ParentCursor: TABSCursor; AllowNotAppliedConditions: Boolean): TABSAO;
   // builds one-table AO
   function BuildOneTableTree(RequestLive: Boolean; ParentQueryAO: TABSAO;
          ParentCursor: TABSCursor; AllowNotAppliedConditions: Boolean): TABSAO;
   // builds multi-tables AO tree
   function BuildMultiTablesTree(RequestLive: Boolean; ParentQueryAO: TABSAO;
          ParentCursor: TABSCursor; AllowNotAppliedConditions: Boolean): TABSAO;
  public
   // creates object
   constructor Create(Lexer: TABSLexer; Query: TDataSet);
   // destroys object
   destructor Destroy; override;
   // parses query
   procedure Parse; override;
   // check if correlated subquery apllied
   function AllConditionsApplied: Boolean; override;
   // builds AO tree
   function BuildAOTree(query: TDataset; RequestLive: Boolean; ParentQueryAO: TABSAO;
                      ParentCursor: TABSCursor; AllowNotAppliedConditions: Boolean): TABSAO; override;
 end;//TABSSQLSelect



////////////////////////////////////////////////////////////////////////////////
//
// TABSQueryExprNode
//
////////////////////////////////////////////////////////////////////////////////

 // select | union | except | ...
 TABSQueryExprNode = class (TObject)
  NodeType:         TABSQueryExprType;  // node is: select | union | except | ...
  Left:             TABSQueryExprNode;  // left node in union, except, ...
  Right:            TABSQueryExprNode;  // right table in union, except, ...
  All:              Boolean;            // [ALL] specified?
  Corresponding:    Boolean;            // [CORRESPONDING] specified?
  CorrespondingFields: TStringList;     // column list
  SelectCommand:    TABSSQLSelect;      // SELECT command (if NodeType is select)

  // creates
  constructor Create; overload;
  // creates copy
  constructor Create(Src: TABSQueryExprNode); overload;
  // destroys
  destructor Destroy; override;
  // adds new node to the tree
  procedure AddNode(NewType: TABSQueryExprType; RightNode: TABSQueryExprNode;
                    bAll, bCorresponding: Boolean; ColumnList: TStringList=nil);
  function AllConditionsApplied: Boolean;
  // check that there is no INTO clause
  function HasInto: Boolean;
 end;

 
 
////////////////////////////////////////////////////////////////////////////////
//
// TABSSQLUnion
// UNION SQL command
//
////////////////////////////////////////////////////////////////////////////////


 TABSSQLUnion = class (TABSSQLCursorCommand)
  protected
   FRootNode: TABSQueryExprNode;         // root node in unions,excepts tree

   // parses [ALL]
   function ParseAll: Boolean;

   // parses [ <corresponding spec> ]
   function ParseCorrespondingSpec(var ColumnsList: TStringList): Boolean;

   // parses SELECT ...
   function ParseQuerySpecification: TABSQueryExprNode;

   // parses <query specification> | <table value constructor>  | <explicit table>
   function ParseSimpleTable: TABSQueryExprNode;

   // parses <simple table> |
   // <left paren> <non-join query expression> <right paren>
   function ParseNonJoinQueryPrimary: TABSQueryExprNode;

   // parses <non-join query primary> |
   // <query term> INTERSECT [ ALL ] [ <corresponding spec> ] <query primary>
   function ParseNonJoinQueryTerm: TABSQueryExprNode;

   // parses <non-join query term> |
   // <query expression> UNION  [ ALL ] [ <corresponding spec> ] <query term> |
   // <query expression> EXCEPT [ ALL ] [ <corresponding spec> ] <query term>
   function ParseNonJoinQueryExpression: TABSQueryExprNode;

   // parses <non-join query expression>  | <joined table>
   function ParseQueryExpression: TABSQueryExprNode;

   // builds AO
   function BuildAO(Node: TABSQueryExprNode; RequestLive: Boolean; ParentQueryAO: TABSAO;
         ParentCursor: TABSCursor; AllowNotAppliedConditions: Boolean): TABSAO;

  public
   // creates object
   constructor Create(Lexer: TABSLexer; Query: TDataSet);
   // destroys object
   destructor Destroy; override;
   // parses query
   procedure Parse; override;
   // check if correlated subquery apllied
   function AllConditionsApplied: Boolean; override;
   // builds AO tree
   function BuildAOTree(query: TDataset; RequestLive: Boolean; ParentQueryAO: TABSAO;
     ParentCursor: TABSCursor; AllowNotAppliedConditions: Boolean): TABSAO; override;

 end;//TABSSQLUnion



////////////////////////////////////////////////////////////////////////////////
//
// TABSSQLInsert
//
////////////////////////////////////////////////////////////////////////////////


 TABSSQLInsert = class (TABSSQLCommand)
  protected
   TableName:             string;  // Table Name
   DatabaseFileName:      string;
   Password:              string;  // table password
   FieldNames:            TStringList;
   FieldValues:           array of TList;
   InternalSelecter:      TABSSQLSelect;
  protected
   procedure ParseValuesList;
  public
   // creates object
   constructor Create(Lexer: TABSLexer; Query: TDataSet);
   // destroys object
   destructor Destroy; override;
   // parses query
   procedure Parse; override;
   // executes query
   procedure ExecSQL(
                      query: TDataset;
                      IsRoot,
                      RequestLive: boolean;
                      var ReadOnly: boolean;
                      ParentQueryAO: TABSAO = nil;
                      ParentCursor: TABSCursor = nil
                     ); override;
 end;//TABSSQLInsert



////////////////////////////////////////////////////////////////////////////////
//
// TABSSQLUpdate
//
////////////////////////////////////////////////////////////////////////////////


 TABSSQLUpdate = class (TABSSQLCommand)
  protected
   DatabaseFileName:          string;  // DB Name
   TableName:             string;  // Table Name
   Password:              string;  // table password
   FieldNames:            TStringList;
   FieldValues:           TList;
   IsFieldValuesSubquery: Boolean;
   FieldValuesSubqueryExpr: TABSExpression;
   InternalSelecter:      TABSSQLSelect;
   FSelectLexer:          TABSLexer;
   FTablePseudonym:       string;
  public
   // creates object
   constructor Create(Lexer: TABSLexer; Query: TDataSet);
   // destroys object
   destructor Destroy; override;
   // parses query
   procedure Parse; override;
   // executes query
   procedure ExecSQL(
                      query: TDataset;
                      IsRoot,
                      RequestLive: boolean;
                      var ReadOnly: boolean;
                      ParentQueryAO: TABSAO = nil;
                      ParentCursor: TABSCursor = nil
                     ); override;
 end;//TABSSQLUpdate



////////////////////////////////////////////////////////////////////////////////
//
// TABSSQLDelete
//
////////////////////////////////////////////////////////////////////////////////


 TABSSQLDelete = class (TABSSQLCommand)
  protected
   InternalSelecter:      TABSSQLSelect;
   TableName:             string;  // Table Name
   FullDelete: boolean;
  public
   // creates object
   constructor Create(Lexer: TABSLexer; Query: TDataSet);
   // destroys object
   destructor Destroy; override;
   // parses query
   procedure Parse; override;
   // executes query
   procedure ExecSQL(
                      query: TDataset;
                      IsRoot,
                      RequestLive: boolean;
                      var ReadOnly: boolean;
                      ParentQueryAO: TABSAO = nil;
                      ParentCursor: TABSCursor = nil
                     ); override;
 end;//TABSSQLDelete



////////////////////////////////////////////////////////////////////////////////
//
// TABSDDLTableManipulation
//
////////////////////////////////////////////////////////////////////////////////


 // FieldDef element
 TSQLFieldDef = class (TObject)
  public
   FieldName:                string;       // field name or pseudonym
   CreateIfNotExists:        Boolean;
   
   // FieldType
   newFieldType:             Boolean;
      FieldType:             TABSAdvancedFieldType;

   // field length ex: String(255)
   newLength:                Boolean;
      Length:                Integer;

   // is Requared field (NOT NULL)
   newRequired:              Boolean;
      Required:              Boolean;

   // Autoinc settings
   newAutoincIncrement:      Boolean;
      AutoincIncrement:      Int64;

   newAutoincLastValue:      Boolean;
      AutoincLastValue:      Int64;

   newAutoincMinValue:       Boolean;
      AutoincMinValue:       Int64;

   newAutoincMaxValue:       Boolean;
      AutoincMaxValue:       Int64;

   newAutoincCycled:         Boolean;
      AutoincCycled:         Boolean;

   // Blob settings
   newBlobBlockSize:         Boolean;
      BlobBlockSize:         Integer; // 1..

   newBlobCompressionAlgorithm: Boolean;
      BlobCompressionAlgorithm: TABSCompressionAlgorithm;// {NONE | ZLIB | BZIP | PPM}

   newBlobCompressionMode:   Boolean;
      BlobCompressionMode:   Byte; //     {0 .. 9}

   //Default Value
   newDefaultValue:          Boolean;
      DefaultValue:          TABSVariant;

   newMinValue:              Boolean;
      MinValue:              TABSVariant;

   newMaxValue:              Boolean;
      MaxValue:              TABSVariant;

   newPrimaryKey:            Boolean;
      PrimaryKey:            Boolean;

   newUnique:                Boolean;
      Unique:                Boolean;

  public
   constructor Create;
   destructor Destroy; override;
 end;

 
 TSQLFieldDefs = class (TObject)
  protected
   List: TList;
   function GetDef(Index: Integer): TSQLFieldDef;
  public
   constructor Create;
   destructor Destroy; override;
   function AddCreated: TSQLFieldDef;
   function GetCount: Integer;
  public
   property Items[Index: Integer]: TSQLFieldDef read GetDef; default;
   property Count: Integer read GetCount;
 end;


 TABSIndexField = record
   FieldName: string;       // field name or pseudonym
   desc:      boolean;      // desc/asc
   nocase:    boolean;      // ncase/case sensitive
 end;

 TABSIndex = record
   IndexName:       string;
   Unique:          Boolean;
   Primary:         Boolean;
   IndexFields:     array of TABSIndexField; // Index Fields
 end;


 TABSDDLTableManipulation = class (TABSSQLCommand)
  protected
   Password:              string;  // table password
   DatabaseName:          string;  // DB Name
   TableName:             string;  // Table Name
   TableAlias:            string;  // Table Pseudonym
   SQLFieldDefs:          TSQLFieldDefs; // SQLFieldDefs
   //PrimaryIndexName:      string;
   //PrimaryKey:            TStringList;
   //PrimaryKeyFields:      array of TABSIndexField;
   //UniqueFields:          TStringList;
   Indexes:               array of TABSIndex;

   // table element list
   procedure ParseTableElementList;
   // parse TableName token
   procedure ParseTableNameToken;
   // Fill table column type into Structure
   procedure FillColumnType(var FieldDef:TSQLFieldDef);
   // Fill column requared value into Structure
   procedure ParseColumnRequared(var FieldDef:TSQLFieldDef);
   // parse PASSWORD 'aaa'
   procedure ParsePassword;
   // parse DEFAULT {const | NULL}
   function ParseDefaultValue(var FieldDef:TSQLFieldDef): Boolean;
   // parse MINVALUE value
   function ParseMinValue(var FieldDef:TSQLFieldDef): Boolean;
   // parse MAXVALUE value
   function ParseMaxValue(var FieldDef:TSQLFieldDef): Boolean;
   // parse fiald ... PRIMARY KEY
   function ParseFieldPrimaryKey(var FieldDef:TSQLFieldDef): Boolean;
   // parse fiald ... UNIQUE
   function ParseFieldUnique(var FieldDef:TSQLFieldDef): Boolean;

   // parse Primary Key
   function ParsePrimaryKey: boolean;
   // parse Index
   function ParseIndex: boolean;
   // parse Index Field
   procedure ParseIndexField(IndexNo: Integer);
   // parse Index Fields
   procedure ParseIndexFields(IndexNo: Integer);
   // go to the Next token and Parse Integer (Int64)
   function ParseInteger: Int64;
   // Create ABSTable object and fill ABSTable params
   procedure CreateInternalABSTable(query: TDataSet; var table: TDataSet;
                            var db: TObject; FillAdvFieldDefs: Boolean);
   // Fill AdvFieldDef
   procedure FillAdvFieldDef(AdvFieldDef: TFieldDef; SQLFieldDef: TSQLFieldDef);
   // Add Indexes to ABSTable
   procedure AddIndexes(T: TDataSet);
   // Add Primary Key into ABSTable
   //procedure AddPrimaryKey(T: TDataSet);
   // Add UNIQUE constraint and index into ABSTable
   //procedure AddUnique(T: TDataSet);
   // Delete PrimaryKey
   procedure DeletePrimaryKey(T: TDataSet);
  public
   // creates object
   constructor Create(Lexer: TABSLexer; Query: TDataSet);
   // destroy
   destructor Destroy; override;
   // parses query
   procedure Parse; override;
   // executes query
   procedure ExecSQL(
                      query: TDataset;
                      IsRoot,
                      RequestLive: boolean;
                      var ReadOnly: boolean;
                      ParentQueryAO: TABSAO = nil;
                      ParentCursor: TABSCursor = nil
                     ); override; abstract;
 end;//TABSDDLTableManipulation




////////////////////////////////////////////////////////////////////////////////
//
// TABSDDLCreateTable
//
////////////////////////////////////////////////////////////////////////////////



 TABSDDLCreateTable = class (TABSDDLTableManipulation)
  private
   FCreateIfNotExists: Boolean;
  protected
   // Create Table
   procedure CreateTable(query: TDataSet);
  public
   // parses query
   procedure Parse; override;
   // executes query
   procedure ExecSQL(
                      query: TDataset;
                      IsRoot,
                      RequestLive: boolean;
                      var ReadOnly: boolean;
                      ParentQueryAO: TABSAO = nil;
                      ParentCursor: TABSCursor = nil
                     ); override;
 end;//TABSDDLCreateTable


////////////////////////////////////////////////////////////////////////////////
//
// TABSDDLDropTable
//
////////////////////////////////////////////////////////////////////////////////


 TABSDDLDropTable = class (TABSDDLTableManipulation)
  protected
   // Drop Table
   procedure DropTable(query: TDataSet);
  public
   // parses query
   procedure Parse; override;
   // executes query
   procedure ExecSQL(
                      query: TDataset;
                      IsRoot,
                      RequestLive: boolean;
                      var ReadOnly: boolean;
                      ParentQueryAO: TABSAO = nil;
                      ParentCursor: TABSCursor = nil
                     ); override;
 end;//TABSDDLDropTable



////////////////////////////////////////////////////////////////////////////////
//
// TABSDDLTruncateTable
//
////////////////////////////////////////////////////////////////////////////////


 TABSDDLTruncateTable = class (TABSDDLTableManipulation)
  protected
   // Truncate Table
   procedure TruncateTable(query: TDataSet);
  public
   // parses query
   procedure Parse; override;
   // executes query
   procedure ExecSQL(
                      query: TDataset;
                      IsRoot,
                      RequestLive: boolean;
                      var ReadOnly: boolean;
                      ParentQueryAO: TABSAO = nil;
                      ParentCursor: TABSCursor = nil
                     ); override;
 end;//TABSDDLTruncateTable



////////////////////////////////////////////////////////////////////////////////
//
// TABSDDLAlterTable
//
////////////////////////////////////////////////////////////////////////////////


 // Types of alter table
 TAlterType = (atAdd, atDrop, atModify, atRenameColumn, atRenameTable);

 TABSDDLAlterTable = class (TABSDDLTableManipulation)
  protected
   // AlterType token
   AlterType:         TAlterType;
   NewPassword:       string;
   NewEncrypted:      Boolean;
   DropColumnNamesList:       TStringList;
   RenameColumnOldNamesList:  TStringList;
   RenameColumnNewNamesList:  TStringList;
   NewTableName:              string;

   // parse AlterType token
   procedure ParseAlterTypeToken;
   // parse NewPassword token
   procedure ParseNewPasswordToken;
   // Parse RenameColumnList
   procedure ParseRenameColumnsList;
   // Alter Table
   procedure DropColumn(T: TDataSet);
   procedure AddColumn(T: TDataSet);
   procedure Modify(T: TDataSet);
   procedure RenameColumn(T: TDataSet);
  public
   // creates object
   constructor Create(Lexer: TABSLexer; Query: TDataSet);
   // destroy
   destructor Destroy; override;
   // parse query
   procedure Parse; override;
   // execute query
   procedure ExecSQL(
                      query: TDataset;
                      IsRoot,
                      RequestLive: boolean;
                      var ReadOnly: boolean;
                      ParentQueryAO: TABSAO = nil;
                      ParentCursor: TABSCursor = nil
                     ); override;
 end;//TABSDDLAlterTable


////////////////////////////////////////////////////////////////////////////////
//
// TABSDDLRenameTable
//
////////////////////////////////////////////////////////////////////////////////

 TABSDDLRenameTable = class (TABSDDLAlterTable)
   // parse query
   procedure Parse; override;
 end;

////////////////////////////////////////////////////////////////////////////////
//
// TABSDDLCreateIndex
//
////////////////////////////////////////////////////////////////////////////////

 TABSDDLCreateIndex = class (TABSSQLCommand)
  protected
   Unique:          boolean;
   IndexName:       string;
   DatabaseFileName:string;
   TableName:       string;
   TableAlias:      string;
   Password:        string;
   InMemory:        Boolean;
   ABSIndexFields:  array of TABSIndexField; // Index Fields
  public
   // creates object
   constructor Create(Lexer: TABSLexer; Query: TDataSet);
   // parse query
   procedure Parse; override;
   // execute query
   procedure ExecSQL(
                      query: TDataset;
                      IsRoot,
                      RequestLive: boolean;
                      var ReadOnly: boolean;
                      ParentQueryAO: TABSAO = nil;
                      ParentCursor: TABSCursor = nil
                     ); override;
 end;//TABSDDLCreateIndex



////////////////////////////////////////////////////////////////////////////////
//
// TABSDDLDropIndex
//
////////////////////////////////////////////////////////////////////////////////


 TABSDDLDropIndex = class (TABSSQLCommand)
  protected
   IndexName: string;
   DatabaseFileName:string;
   TableName:       string;
   TableAlias:      string;
   Password:        string;
   InMemory:        Boolean;
  public
   // creates object
   constructor Create(Lexer: TABSLexer; Query: TDataSet);
   // parse query
   procedure Parse; override;
   // execute query
   procedure ExecSQL(
                      query: TDataset;
                      IsRoot,
                      RequestLive: boolean;
                      var ReadOnly: boolean;
                      ParentQueryAO: TABSAO = nil;
                      ParentCursor: TABSCursor = nil
                     ); override;
 end;//TABSDDLDropIndex


////////////////////////////////////////////////////////////////////////////////
//
// TABSSQLStartTransaction
//
////////////////////////////////////////////////////////////////////////////////


 TABSSQLStartTransaction = class (TABSSQLCommand)
  public
   // parse query
   procedure Parse; override;
   // execute query
   procedure ExecSQL(
                      query: TDataset;
                      IsRoot,
                      RequestLive: boolean;
                      var ReadOnly: boolean;
                      ParentQueryAO: TABSAO = nil;
                      ParentCursor: TABSCursor = nil
                     ); override;
 end;//TABSSQLStartTransaction



////////////////////////////////////////////////////////////////////////////////
//
// TABSSQLCommit
//
////////////////////////////////////////////////////////////////////////////////


 TABSSQLCommit = class (TABSSQLCommand)
  private
   FFlush: Boolean;
  public
   // parse query
   procedure Parse; override;
   // execute query
   procedure ExecSQL(
                      query: TDataset;
                      IsRoot,
                      RequestLive: boolean;
                      var ReadOnly: boolean;
                      ParentQueryAO: TABSAO = nil;
                      ParentCursor: TABSCursor = nil
                     ); override;
 end;//TABSSQLCommit



////////////////////////////////////////////////////////////////////////////////
//
// TABSSQLRollback
//
////////////////////////////////////////////////////////////////////////////////


 TABSSQLRollback = class (TABSSQLCommand)
  public
   // parse query
   procedure Parse; override;
   // execute query
   procedure ExecSQL(
                      query: TDataset;
                      IsRoot,
                      RequestLive: boolean;
                      var ReadOnly: boolean;
                      ParentQueryAO: TABSAO = nil;
                      ParentCursor: TABSCursor = nil
                     ); override;
 end;//TABSSQLRollback




 // set field value from ABSVariant
 procedure SetABSVariantIntoField(Value: TABSVariant; Field: TField);



implementation


uses ABSMain, ABSBaseEngine;





////////////////////////////////////////////////////////////////////////////////
//
// TABSSQLProcessor
//
////////////////////////////////////////////////////////////////////////////////


//------------------------------------------------------------------------------
// Constructor
//------------------------------------------------------------------------------
constructor TABSSQLProcessor.Create(Query: TDataSet);
begin
  FSQLParams := TABSSQLParams.Create;
  ABSQuery := Query;
  FCursor := nil;
  FSqlText := '';
  SetLength(queries, 0);
  FLex := nil;
end;//Create


//------------------------------------------------------------------------------
// Destructor
//------------------------------------------------------------------------------
destructor TABSSQLProcessor.Destroy;
begin
  FSQLParams.Free;
  if (FLex <> nil) then
    FLex.Free;
  ClearQueries;
end;//Destroy


//------------------------------------------------------------------------------
// PrepareStatement
//------------------------------------------------------------------------------
procedure TABSSQLProcessor.PrepareStatement(SQLText: PChar);
begin
  FSqlText := SQLText;
end;//PrepareStatement


//------------------------------------------------------------------------------
// OpenQuery
//------------------------------------------------------------------------------
function TABSSQLProcessor.OpenQuery: TABSCursor;
begin
  FRowsAffected := 0;
  Result := nil;
  ClearQueries;

  if (FLex <> nil) then
    FLex.Free;
  FLex := TABSLexer.Create(FSqlText, FSQLParams);
  while ParseNextCommand(FLex) do
    begin
      Queries[0].ExecSQL(ABSQuery, True, FRequestLive, FReadonly);
      FRowsAffected := RowsAffected + Queries[0].RowsAffected;
    end;//if
  if (Queries[0] <> nil) then
    begin
      Result := Queries[0].GetResultCursor;
      if (Result <> nil) then
        Result.InternalFirst;
    end;

end;//OpenQuery


//------------------------------------------------------------------------------
// ExecuteQuery
//------------------------------------------------------------------------------
procedure TABSSQLProcessor.ExecuteQuery;
begin
  OpenQuery;
end;//ExecuteQuery


//------------------------------------------------------------------------------
// parse SQL script and fullfill SQL command objects
//------------------------------------------------------------------------------
function TABSSQLProcessor.ParseNextCommand(Lexer: TABSLexer): Boolean;
var
  Token: TToken;
begin

 if (Lexer.NumCommands = 0) then
  raise EABSException.Create(30145, ErrorGBlankSqlCommand);

 if Lexer.GetNextCommand then
  begin
   Result := True;

   // free prior query object
   if (Length(Queries) > 0) then
     begin
        Queries[0].Free;
        Queries[0] := nil;
        SetLength(Queries, 0);
     end;

   // look at first token
   if (not Lexer.GetCurrentToken(Token)) then
    raise EABSException.Create(30146, ErrorGBlankSqlCommand);

   if (Token.TokenType = tktReservedWord) then
     case Token.ReservedWord of
     rwSELECT:  // SELECT
                AddSelectQuery(Lexer);
     rwINSERT:  // INSERT
                AddInsertQuery(Lexer);
     rwUPDATE:  // UPDATE
                AddUpdateQuery(Lexer);
     rwDELETE:  // DELETE
                AddDeleteQuery(Lexer);
     rwTRUNCATE:// TRUNCATE TABLE
                AddTruncateTableQuery(Lexer);
     rwCREATE:  // CREATE
                begin
                  if Lexer.GetNextToken(Token) then
                    case Token.ReservedWord of
                      rwTABLE:  // CREATE TABLE
                                AddCreateTableQuery(Lexer);
                      rwINDEX,
                      rwUNIQUE: // CREATE INDEX or UNIQUE INDEX
                                AddCreateIndex(Lexer);
                      else raise EABSException.Create(30147, ErorrGObjectTypeKeywordExpected,
                             [Token.Text, Token.LineNum, Token.ColumnNum]);
                    end
                  else raise EABSException.Create(30148, ErrorGUnexpectedEndOfCommand,
                            [Token.LineNum, Token.ColumnNum]);
                end;
      rwDROP:   // DROP
                begin
                  if Lexer.GetNextToken(Token) then
                    case Token.ReservedWord of
                      rwTABLE:  // DROP TABLE
                                AddDropTableQuery(Lexer);
                      rwINDEX:  // DROP INDEX
                                AddDropIndexQuery(Lexer);
                      else raise EABSException.Create(30149, ErorrGObjectTypeKeywordExpected,
                             [Token.Text, Token.LineNum, Token.ColumnNum]);
                    end
                  else raise EABSException.Create(30150, ErrorGUnexpectedEndOfCommand,
                            [Token.LineNum, Token.ColumnNum]);
                end;
      rwALTER:   // ALTER
                begin
                  if Lexer.GetNextToken(Token) then
                    case Token.ReservedWord of
                      rwTable:  // ALTER TABLE
                                AddAlterTableQuery(Lexer);
                      else raise EABSException.Create(30151, ErorrGObjectTypeKeywordExpected,
                             [Token.Text, Token.LineNum, Token.ColumnNum]);

                    end
                  else raise EABSException.Create(30152, ErrorGUnexpectedEndOfCommand,
                            [Token.LineNum, Token.ColumnNum]);
                end;
      rwRENAME:   // RENAME
                begin
                  if (not Lexer.GetNextToken(Token)) then
                    raise EABSException.Create(30353, ErrorGUnexpectedEndOfCommand,
                                               [Token.LineNum, Token.ColumnNum]);
                  if IsReservedWord(Token, rwTABLE) then
                    Lexer.GetNextToken(Token);
                  // RENAME TABLE
                  AddRenameTableQuery(Lexer)
                end;
      rwSTART:    // START TRANSACTION
                  AddStartTransaction(Lexer);
      rwCOMMIT:   // COMMIT
                  AddCommit(Lexer);
      rwROLLBACK: // ROLLBACK
                  AddRollback(Lexer);

     else       // unsupported SQL or unexpected token
        raise EABSException.Create(30153, ErrorGSQLCommandExpected,
                                 [Token.Text, Token.LineNum, Token.ColumnNum]);
     end
   else
    // '( SELECT ...) ' ?
    if (Token.TokenType = tktLeftParenthesis) then
     AddSelectQuery(Lexer)
    else
     raise EABSException.Create(30154, ErrorGSQLCommandExpected,
                         [Token.Text, Token.LineNum, Token.ColumnNum]);
  end
 else
   Result := False;
end;// Parse


//------------------------------------------------------------------------------
// adds SELECT query object
//------------------------------------------------------------------------------
procedure TABSSQLProcessor.AddSelectQuery(Lexer: TABSLexer);
var
  OldQueryCount: Integer;
begin
  OldQueryCount := Length(Queries);
  SetLength(Queries, OldQueryCount+1);
  Queries[OldQueryCount] := TABSSQLUnion.Create(Lexer, ABSQuery);
  if (Queries[OldQueryCount].GetCurrentToken) then
    raise EABSException.Create(20260, ErrorAEndOfCommandExpected,
                                 [Queries[OldQueryCount].Token.Text, Queries[OldQueryCount].Token.LineNum, Queries[OldQueryCount].Token.ColumnNum]);
end;// AddSelectQuery


//------------------------------------------------------------------------------
// adds INSERT query object
//------------------------------------------------------------------------------
procedure TABSSQLProcessor.AddInsertQuery(Lexer: TABSLexer);
var
  OldQueryCount: Integer;
begin
  OldQueryCount := Length(Queries);
  SetLength(Queries, OldQueryCount+1);
  Queries[OldQueryCount] := TABSSQLInsert.Create(Lexer, ABSQuery);
end;//AddInsertQuery


//------------------------------------------------------------------------------
// adds UPDATE query object
//------------------------------------------------------------------------------
procedure TABSSQLProcessor.AddUpdateQuery(Lexer: TABSLexer);
var
  OldQueryCount: Integer;
begin
  OldQueryCount := Length(Queries);
  SetLength(Queries, OldQueryCount+1);
  Queries[OldQueryCount] := TABSSQLUpdate.Create(Lexer, ABSQuery);
end;//AddUpdateQuery


//------------------------------------------------------------------------------
// adds DELETE query object
//------------------------------------------------------------------------------
procedure TABSSQLProcessor.AddDeleteQuery(Lexer: TABSLexer);
var
  OldQueryCount: Integer;
begin
  OldQueryCount := Length(Queries);
  SetLength(Queries, OldQueryCount+1);
  Queries[OldQueryCount] := TABSSQLDelete.Create(Lexer, ABSQuery);
end;//AddDeleteQuery


//------------------------------------------------------------------------------
// adds TRUNCATE TABLE query object
//------------------------------------------------------------------------------
procedure TABSSQLProcessor.AddTruncateTableQuery(Lexer: TABSLexer);
var
  OldQueryCount: Integer;
begin
  OldQueryCount := Length(Queries);
  SetLength(Queries, OldQueryCount+1);
  Queries[OldQueryCount] := TABSDDLTruncateTable.Create(Lexer, ABSQuery);
end;//AddTruncateTableQuery


//------------------------------------------------------------------------------
// adds CREATE TABLE query object
//------------------------------------------------------------------------------
procedure TABSSQLProcessor.AddCreateTableQuery(Lexer: TABSLexer);
var
  OldQueryCount: Integer;
begin
  OldQueryCount := Length(Queries);
  SetLength(Queries, OldQueryCount+1);
  Queries[OldQueryCount] := TABSDDLCreateTable.Create(Lexer, ABSQuery);
  //if (FLex.GetNextToken(Token)) then
  // // unexpected token
  // raise ETblException.Create(02003,
  //                 [Token.Text, Token.LineNum, Token.ColumnNum]);
  //
end;//AddCreateTableQuery


//------------------------------------------------------------------------------
// adds DROP TABLE query object
//------------------------------------------------------------------------------
procedure TABSSQLProcessor.AddDropTableQuery(Lexer: TABSLexer);
var
  OldQueryCount: Integer;
begin
  OldQueryCount := Length(Queries);
  SetLength(Queries, OldQueryCount+1);
  Queries[OldQueryCount] := TABSDDLDropTable.Create(Lexer, ABSQuery);
end;//AddDropTableQuery


//------------------------------------------------------------------------------
// add ALTER TABLE query object
//------------------------------------------------------------------------------
procedure TABSSQLProcessor.AddAlterTableQuery;
var
  OldQueryCount: Integer;
begin
  OldQueryCount := Length(Queries);
  SetLength(Queries, OldQueryCount+1);
  Queries[OldQueryCount] := TABSDDLAlterTable.Create(Lexer, ABSQuery);
end;//AddAlterTableQuery


//------------------------------------------------------------------------------
// add RENAME TABLE
//------------------------------------------------------------------------------
procedure TABSSQLProcessor.AddRenameTableQuery(Lexer: TABSLexer);
var
  OldQueryCount: Integer;
begin
  OldQueryCount := Length(Queries);
  SetLength(Queries, OldQueryCount+1);
  Queries[OldQueryCount] := TABSDDLRenameTable.Create(Lexer, ABSQuery);
end;//AddRenameTableQuery


//------------------------------------------------------------------------------
// add CREATE INDEX query object
//------------------------------------------------------------------------------
procedure TABSSQLProcessor.AddCreateIndex;
var
  OldQueryCount: Integer;
begin
  OldQueryCount := Length(Queries);
  SetLength(Queries, OldQueryCount+1);
  Queries[OldQueryCount] := TABSDDLCreateIndex.Create(Lexer, ABSQuery);
end;//AddCreateIndex


//------------------------------------------------------------------------------
// add DROP INDEX query object
//------------------------------------------------------------------------------
procedure TABSSQLProcessor.AddDropIndexQuery;
var
  OldQueryCount: Integer;
begin
  OldQueryCount := Length(Queries);
  SetLength(Queries, OldQueryCount+1);
  Queries[OldQueryCount] := TABSDDLDropIndex.Create(Lexer, ABSQuery);
end;//AddDropIndexQuery


//------------------------------------------------------------------------------
// add START TRANSACTION
//------------------------------------------------------------------------------
procedure TABSSQLProcessor.AddStartTransaction(Lexer: TABSLexer);
var
  OldQueryCount: Integer;
begin
  OldQueryCount := Length(Queries);
  SetLength(Queries, OldQueryCount+1);
  Queries[OldQueryCount] := TABSSQLStartTransaction.Create(Lexer, ABSQuery);
end;//AddStartTransaction



//------------------------------------------------------------------------------
// add COMMIT
//------------------------------------------------------------------------------
procedure TABSSQLProcessor.AddCommit(Lexer: TABSLexer);
var
  OldQueryCount: Integer;
begin
  OldQueryCount := Length(Queries);
  SetLength(Queries, OldQueryCount+1);
  Queries[OldQueryCount] := TABSSQLCommit.Create(Lexer, ABSQuery);
end;//AddStartTransaction



//------------------------------------------------------------------------------
// add ROLLBACK
//------------------------------------------------------------------------------
procedure TABSSQLProcessor.AddRollback(Lexer: TABSLexer);
var
  OldQueryCount: Integer;
begin
  OldQueryCount := Length(Queries);
  SetLength(Queries, OldQueryCount+1);
  Queries[OldQueryCount] := TABSSQLRollback.Create(Lexer, ABSQuery);
end;//AddStartTransaction


//------------------------------------------------------------------------------
// ClearQueries
//------------------------------------------------------------------------------
procedure TABSSQLProcessor.ClearQueries;
var i: integer;
begin
  for i := 0 to Length(Queries)-1 do
   if (Queries[i] <> nil) then
     FreeAndNil(Queries[i]);
  SetLength(Queries, 0);
end;//ClearQueries


////////////////////////////////////////////////////////////////////////////////
//
// TABSSQLSelect
//
////////////////////////////////////////////////////////////////////////////////


//------------------------------------------------------------------------------
// Constructor
//------------------------------------------------------------------------------
constructor TABSSQLSelect.Create(Lexer: TABSLexer; Query: TDataSet);
begin
  SelectListCount := 0;
  SetLength(SelectList, 0);
  FromTablesCount := 0;
  SetLength(FromTables, 0);
  OrderBySpecsCount := 0;
  SetLength(OrderBySpecs, 0);
  SearchCondition := nil;
  HavingCondition := nil;
  GroupByFields := TABSFields.Create;
  inherited Create(Lexer, Query);
end;// Create


//------------------------------------------------------------------------------
// Destructor
//------------------------------------------------------------------------------
destructor TABSSQLSelect.Destroy;
var
  i: integer;
begin
  for i := 0 to SelectListCount-1 do
    if (SelectList[i].ValueExpr <> nil) then
     begin
      SelectList[i].ValueExpr.Free;
      SelectList[i].ValueExpr := nil;
     end;

  if (SearchCondition <> nil) then
   SearchCondition.Free;
  if (GroupByFields <> nil) then
   GroupByFields.Free;
  if (HavingCondition <> nil) then
   HavingCondition.Free;

  for i := 0 to FromTablesCount-1 do
   FromTables[i].Free;

  inherited;
end;// Destroy


//------------------------------------------------------------------------------
// check if correlated subquery apllied
//------------------------------------------------------------------------------
function TABSSQLSelect.AllConditionsApplied: Boolean;
begin
  Result := not FSubqueryHasNotAppliedConditions;

  if (SearchCondition <> nil) then
    begin
     if (not SearchCondition.IsEmpty) then
       Result := False;
    end;
  if (HavingCondition <> nil) then
    begin
      if (not HavingCondition.IsEmpty) then
        Result := False;
    end;
end;// AllConditionsApplied


//------------------------------------------------------------------------------
// builds AO tree
//------------------------------------------------------------------------------
function TABSSQLSelect.BuildAOTree(query: TDataset; RequestLive: Boolean; ParentQueryAO: TABSAO;
          ParentCursor: TABSCursor; AllowNotAppliedConditions: Boolean): TABSAO;
var
  i, j: integer;
  bAnyExpressions: Boolean;
  bGroupBy: Boolean;
  GroupByAO: TABSAO;
  bAddTempTableAO: Boolean;

begin
  ResultDataset := query;
  FSubqueryHasNotAppliedConditions := False;

  // live result with 1 table?
  if ((FromTablesCount = 1) and (FromTables[0].TableType = attTable)) then
    Result := BuildOneTableTree(RequestLive, ParentQueryAO, ParentCursor, AllowNotAppliedConditions)
  else
    Result := BuildMultiTablesTree(RequestLive, ParentQueryAO, ParentCursor, AllowNotAppliedConditions);

 try
  // create group by AO?
  bGroupBy := (GroupByFields.ItemCount > 0);
  // check for aggregates in select list
  if (not bGroupBy) then
   if (not AllFields) then
    begin
     for i := 0 to SelectListCount-1 do
      if (SelectList[i].ValueExpr <> nil) then
       if (TABSExpression(SelectList[i].ValueExpr).IsAggregated) then
        begin
         bGroupBy := True;
         break;
        end;
    end;

  // if expressions, then AOTable -> AOTableExpr?
  if (bGroupBy) or (Result is TABSAOTable) then
   begin
    bAnyExpressions := False;
    for i := 0 to SelectListCount-1 do
     if (SelectList[i].ValueExpr <> nil) then
      // 5.01 fix to support "select f1,f1 from table1"
      if (not TABSExpression(SelectList[i].ValueExpr).IsField) or SelectList[i].IsDuplicatedField then
        if (not TABSExpression(SelectList[i].ValueExpr).IsAggregated) then
         begin
          bAnyExpressions := True;
          break;
         end;
    // create table wrapper with expression-fields?
    if (bAnyExpressions) then
      begin
        if (bGroupBy) then
          begin
            bAnyExpressions := False;
            // check if group by expression, not only by fields
            for i := 0 to SelectListCount-1 do
             if (SelectList[i].ValueExpr <> nil) then
              if (not TABSExpression(SelectList[i].ValueExpr).IsField) then
                if (not TABSExpression(SelectList[i].ValueExpr).IsAggregated) then
                  begin
                    for j := 0 to GroupByFields.ItemCount-1 do
                      if (UpperCase(GroupByFields.Items[j].FieldName) =
                          UpperCase(SelectList[i].Pseudonym)) then
                        begin
                          bAnyExpressions := True;
                          break;
                        end;
                  end;
            if (bAnyExpressions) then
              begin
                Result := TABSAOTableExpr.Create(Result);
                Result.SetResultFields(SelectList,False);
              end;
          end
        else
          Result := TABSAOTableExpr.Create(Result);
      end;
   end;

  // apply parts of WHERE clause if possible
  if (SearchCondition <> nil) then
     SearchCondition.ApplyFilterParts(Result, ParentQueryAO, ParentCursor);

  // create GroupByAO, apply having condition
  if (bGroupBy) then
   begin
    try
     Result := TABSAOGroupBy.Create(Result, GroupByFields, SelectList);
     GroupByAO := Result;
    except
     // to avoid duplicated destroying
     Result := nil;
     for i := 0 to SelectListCount-1 do
      if (SelectList[i].ValueExpr <> nil) then
       begin
        SelectList[i].ValueExpr.Free;
        SelectList[i].ValueExpr := nil;
       end;
     raise;
    end;
   end;

  // prepare for setting projection
  if (Result.FHasSetResultFields) then
    Result := TABSAOTableExpr.Create(Result);

  bAddTempTableAO := (not RequestLive) and (Result is TABSAOTable) and (TopRowCount < 0);
  // sets projection
  Result.SetResultFields(SelectList,Distinct and (not bAddTempTableAO));

  if (bGroupBy) then
    begin
      // apply having
      if (HavingCondition <> nil) then
        begin
          // apply having with pseudonames
          HavingCondition.ApplyFilterParts(Result, ParentQueryAO, ParentCursor, false, true);
          // apply having w/o pseudonames
          HavingCondition.ApplyFilterParts(GroupByAO, ParentQueryAO, ParentCursor, true, true);
          if (not AllowNotAppliedConditions) then
            if (not HavingCondition.IsEmpty) then
               raise EABSException.Create(20244, ErorrANotApplicableHavingCondition);
        end;
    end;

  // sort - order by
  ApplyOrderBy(Result);

  // apply pseudonames parts of WHERE clause if possible
  if (SearchCondition <> nil) then
   begin
     SearchCondition.ApplyFilterParts(Result, ParentQueryAO, ParentCursor);
     if (not AllowNotAppliedConditions) then
       if (not SearchCondition.IsEmpty) then
         raise EABSException.Create(30144, ErorrGNotApplicableCondition);
   end;

  // TOP n?
  if (TopRowCount > -1) then
   begin
    Result := TABSAOSQLTopRowCount.Create(Result);
    Result.SetTopRowCount(FirstRowNo, TopRowCount);
   end;

  // if not request live => always use temp table
  if (bAddTempTableAO) then
    begin
      Result := TABSAOTableExpr.Create(Result);
      Result.SetResultFields(SelectList,Distinct and bAddTempTableAO);
    end;

 except
  if (Assigned(Result)) then
   Result.Free;
  raise;
 end;
end;//BuildAOTree


//------------------------------------------------------------------------------
// SELECT
//------------------------------------------------------------------------------
function TABSSQLSelect.ParseSelectToken: Boolean;
begin
  Result := IsReservedWord(Token, rwSELECT);
  if (not Result) then
   // unsupported SQL or unexpected token
   raise EABSException.Create(30156, ErrorGSQLCommandExpected,
         [Token.Text, Token.LineNum, Token.ColumnNum]);
  GetNextToken;
end;//ParseSelectToken


//------------------------------------------------------------------------------
// DISTINCT | ALL ?
//------------------------------------------------------------------------------
function TABSSQLSelect.ParseSetQuantifier: Boolean;
begin
 if (IsReservedWord(Token)) then
  begin
    if (IsReservedWord(Token, rwALL)) then
     begin
      Distinct := False;
      Result := True;
     end
    else
    if (IsReservedWord(Token, rwDISTINCT)) then
     begin
      Distinct := True;
      Result := True;
     end
    else // unrecognized reserved word - give up to handle
     begin
      Distinct := False; // default value
      Result := False; // handle this token by another handler
     end;
  end
 else // not reserved-word token give up handling
  begin
    Distinct := False; // default value
    Result := False; // handle this word by another handler
  end;

 // get next token
 if (Result) then
  GetNextToken;
end;// ParseSetQuantifier


//------------------------------------------------------------------------------
// TOP n ?
//------------------------------------------------------------------------------
function TABSSQLSelect.ParseTopOperator: Boolean;
begin
  if (IsReservedWord(Token, rwTOP)) then
   begin
    // suppose 'TOP n'
    GetNextToken;
    // integer?
    if (not (Token.TokenType in [tktInt,tktParameter]) ) then
     raise EABSException.Create(30158, ErrorGIntegerExpected,
                             [Token.Text, Token.LineNum, Token.ColumnNum]);
    if (Token.TokenType = tktInt) then
      TopRowCount := StrToInt(Token.Text)
    else
      TopRowCount := Token.ParamValue.AsInteger;
    // get next token
    GetNextToken;
    // default value
    FirstRowNo := -1;
    // ','? => TOP n, first_row
    if (Token.Text = ',') then
     begin
       // get next token
       GetNextToken;
       // integer?
       if (not (Token.TokenType in [tktInt,tktParameter]) ) then
         raise EABSException.Create(30159, ErrorGIntegerExpected,
                             [Token.Text, Token.LineNum, Token.ColumnNum]);
       if (Token.TokenType = tktInt) then
         FirstRowNo := StrToInt(Token.Text)
       else
         FirstRowNo := Token.ParamValue.AsInteger;
       // get next token
       GetNextToken;
     end;
    Result := True;
   end
  else // unrecognized reserved word - give up to handle
   begin
    TopRowCount := -1; // default value
    FirstRowNo := -1;
    Result := False; // handle this token by another handler
   end;
end;// ParseTopOperator


//------------------------------------------------------------------------------
// DuplicatedFieldExists
//------------------------------------------------------------------------------
function TABSSQLSelect.DuplicatedFieldExists(const TableName, FieldName: string): Boolean;
var
  i: Integer;
begin
  Result := False;
  for i:=0 to SelectListCount-1 do
    if (not SelectList[i].IsExpression) then
      if (UpperCase(SelectList[i].TableName) = UpperCase(TableName)) and
          (UpperCase(SelectList[i].FieldName) = UpperCase(FieldName)) then
        begin
          Result := True;
          break;
        end;
end;// DuplicatedFieldExists


//------------------------------------------------------------------------------
// FieldExists
//------------------------------------------------------------------------------
function TABSSQLSelect.FieldExists(const TableName, FieldName: string): Boolean;
var
  i: Integer;
begin
  Result := False;
  for i:=0 to SelectListCount-1 do
      if (UpperCase(SelectList[i].TableName) = UpperCase(TableName)) and
         ((UpperCase(SelectList[i].FieldName) = UpperCase(FieldName)) or
          (UpperCase(SelectList[i].Pseudonym) = UpperCase(FieldName)))then
        begin
          Result := True;
          break;
        end;
end;// FieldExists


//------------------------------------------------------------------------------
// GetFieldNameForDuplicatedField
//------------------------------------------------------------------------------
function TABSSQLSelect.GetFieldNameForDuplicatedField(const TableName, FieldName: string): string;
var
  i: Integer;
begin
  i := 0;
  repeat
    Inc(i);
    Result := Format('%s_%d',[FieldName,i]);
  until (not FieldExists(TableName, Result));
end;// GetFieldNameForDuplicatedField


//------------------------------------------------------------------------------
// <derived column>  | <qualifier> <period> <asterisk>
//------------------------------------------------------------------------------
function TABSSQLSelect.ParseSelectSubList: Boolean;
var
  FieldName, TableName, Pseudonym: string;
  AllFields: Boolean;
begin
 // handle field1 | table1.field1 | expr
  begin
   Result := True;

   // add new select list item
   inc(SelectListCount);
   SetLength(SelectList, SelectListCount);

   // parse field name | expr
   SelectList[SelectListCount-1].ValueExpr := TABSExpression.Create;

   TABSExpression(SelectList[SelectListCount-1].ValueExpr).InMemory :=
                                                     TABSQuery(LQuery).InMemory;

   TABSExpression(SelectList[SelectListCount-1].ValueExpr).ParseForValueExpression(
                                LQuery, LLex);

   if (TABSExpression(SelectList[SelectListCount-1].ValueExpr).IsEmpty) then
    begin
     SelectList[SelectListCount-1].ValueExpr.Free;
     SelectList[SelectListCount-1].ValueExpr := nil;
     raise EABSException.Create(30160, ErrorGExpressionExpected,
                             [Token.Text, Token.LineNum, Token.ColumnNum]);
    end;
   GetCurrentToken;

   // pseudonym is not specified yet
   Pseudonym := '';
   AllFields := False;
   // check pseudonym
   if (not AllFields) then
    // (Field1 AS F1) ?
          if (IsReservedWord(Token, rwAS)) then
           begin
             // get next token (F1?)
             GetNextToken([tktString, tktQuotedString, tktBracketedString],
                          30162, ErrorGFieldPseudonymExpected);
             // got pseudonym
             Pseudonym := Token.Text;
             // get next token
             GetNextToken;
            end
          else
          // (Field1 F1) ?
          if (Token.TokenType in
               [tktString, tktQuotedString, tktBracketedString]) then
            begin
             // got pseudonym
             Pseudonym := Token.Text;
             // look at next token
             GetNextToken;
            end;

   // expr or field?
   SelectList[SelectListCount-1].IsExpression :=
          not TABSExpression(SelectList[SelectListCount-1].ValueExpr).IsField;

   // field?
   if (not SelectList[SelectListCount-1].IsExpression) then
    begin
     // get field name, table name
     TABSExpression(SelectList[SelectListCount-1].ValueExpr).GetFieldInfo(TableName, FieldName);
     // treat duplicated field as expression
     if (FieldName = '*') or (not DuplicatedFieldExists(TableName, FieldName)) then
       begin
         // store TableName
         SelectList[SelectListCount-1].TableName := TableName;
         // store ValueExpr (FieldName)
         SelectList[SelectListCount-1].FieldName := FieldName;
         if (FieldName = '*') then
          AllFields := True;
         SelectList[SelectListCount-1].IsDuplicatedField := False;
       end
     else
       begin
         SelectList[SelectListCount-1].IsExpression :=True;
         SelectList[SelectListCount-1].IsDuplicatedField := True;
         if (Pseudonym = '') then
           Pseudonym := GetFieldNameForDuplicatedField(TableName, FieldName);
       end;
    end;

   // free non-expr objects (expr objects are freed by AO)
   if (not SelectList[SelectListCount-1].IsExpression) then
    begin
     TABSExpression(SelectList[SelectListCount-1].ValueExpr).Free;
     TABSExpression(SelectList[SelectListCount-1].ValueExpr) := nil;
    end;

   // store AllFields ('table1.*'?)
   SelectList[SelectListCount-1].AllFields := AllFields;
   // pseudonym
   SelectList[SelectListCount-1].Pseudonym := Pseudonym;
  end;
end;// ParseSelectSubList


//------------------------------------------------------------------------------
// * | <select sublist>
//------------------------------------------------------------------------------
function TABSSQLSelect.ParseSelectList: Boolean;
begin
  if (Token.Text = Asterisk) then
   begin
    Result := True;
    GetNextToken;
    AllFields := True; // select all fields
   end
  else
   begin
    AllFields := False;
    Result := True;
     // get FieldName
    if (ParseSelectSubList) then
     // all field expressions
     repeat
        if (Token.TokenType <> tktComma) then
           break
        else
          // skip ',' token
          GetNextToken;
        // get next field expr
        if (not ParseSelectSubList) then
         raise EABSException.Create(30163, ErrorGFieldNameExpected,
               [Token.Text, Token.LineNum, Token.ColumnNum]);
     until False
    else
     raise EABSException.Create(30164, ErrorGFieldListExpected,
                        [Token.Text, Token.LineNum, Token.ColumnNum]);
   end;
end;//ParseSelectList


//------------------------------------------------------------------------------
// INTO <target>
//------------------------------------------------------------------------------
function TABSSQLSelect.ParseInto: Boolean;
begin
 Result := False;
 IntoTable := '';
 IntoDatabase := '';
 IntoMemory := False;
 IntoImmediate := False;

 if (IsReservedWord(Token, rwINTO)) then
  begin
    // get table | database name
    GetNextToken([tktString, tktParameter, tktReservedWord, tktBracketedString, tktQuotedString],
                 30165, ErrorGTableNameExpected);
    if (IsReservedWord(Token, rwMEMORY)) then
     begin
      IntoMemory := True;
      // next token
      GetNextToken;
      if (IsReservedWord(Token, rwIMMEDIATE)) then
        begin
          IntoImmediate := True;
          // next token
          GetNextToken;
        end;
     end;

    // into table_name
    if (Token.TokenType <> tktParameter) then
      IntoTable := Token.Text
    else
      IntoTable := string(Token.ParamValue.AsAnsiString);

    // next token
    GetNextToken;
    // db.table?
    if (Token.TokenType = tktDot) then
     begin
      IntoDatabase := IntoTable;
      // get table name
      GetNextToken([tktString, tktParameter, tktBracketedString, tktQuotedString],
                   30166, ErrorGTableNameExpected);
      if (Token.TokenType <> tktParameter) then
        IntoTable := Token.Text
      else
        IntoTable := string(Token.ParamValue.AsAnsiString);
      // next token
      GetNextToken;
     end;
    Result := True;
  end;
end;// ParseInto


//------------------------------------------------------------------------------
// ON <join condition>
//------------------------------------------------------------------------------
function TABSSQLSelect.ParseJoinCondition(var SearchCondition: TABSExpression): Boolean;
begin
 Result := False;
 if (IsReservedWord(Token, rwON)) then
   begin
    Result := True;
    GetNextToken; // skip ON token
    //SearchCondition := TABSExpression.Create(LLex);
    SearchCondition := TABSExpression.Create;
    //SearchCondition.ParseSearchExpression;
    SearchCondition.ParseForBooleanExpression(LQuery, LLex);
    if (not AllFields) then
     SearchCondition.ReplacePseudonyms(SelectList);
    // get current unhandled token
    GetCurrentToken;
   end;
end;// ParseJoinCondition


//------------------------------------------------------------------------------
// USING <join columns>
//------------------------------------------------------------------------------
function TABSSQLSelect.ParseNamedColumnsJoin(var Fields: TStringList): Boolean;
begin
 Result := False;
 if (IsReservedWord(Token, rwUSING)) then
   begin
    Result := True;
    GetNextToken; // skip USING token

    // '(' ?
    if (Token.TokenType = tktLeftParenthesis) then
     begin
      // skip '('
      GetNextToken;

      // parse fields list
      ParseColumnList(Fields);

      // skip ')'
      if (Token.TokenType = tktRightParenthesis) then
         GetNextToken; // end of query is possible here
     end
    else
     raise EABSException.Create(30167, ErrorGOtherTokenExpected,
                   ['(', Token.Text, Token.LineNum, Token.ColumnNum]);
   end;
end;// ParseNamedColumnsJoin


//------------------------------------------------------------------------------
// CROSS JOIN | INNER JOIN | ...
//------------------------------------------------------------------------------
function TABSSQLSelect.ParseJoin(var tblRef: TABSTableReference): Boolean;
var
 RightTblRef: TABSTableReference;
 JoinType: TABSJoinType;
 IsNatural: Boolean;
 Fields: TStringList;
 SearchCondition: TABSExpression;
begin
 Result := False;

 if (IsReservedWord(Token)) then
  repeat
     IsNatural := False; // default value
     JoinType := ajtInner; // default value
     // parse CROSS JOIN | [ NATURAL ] [ <join type> ] JOIN
     if (IsReservedWord(Token, rwCROSS)) then
      begin
          GetNextToken([tktReservedWord], 30168, ErrorGUnexpectedToken); // skip CROSS
          JoinType := ajtCross;
          Result := True;
      end
     else
      begin
       // [NATURAL]
       if (IsReservedWord(Token, rwNATURAL)) then
         begin
          IsNatural := True;
          GetNextToken([tktReservedWord], 30169, ErrorGUnexpectedToken);
          Result := True;
         end;
       // INNER?
       if (IsReservedWord(Token, rwINNER)) then
         begin
          JoinType := ajtInner;
          GetNextToken([tktReservedWord], 30170, ErrorGUnexpectedToken);
          Result := True;
         end
       else
        // OUTER join
        begin
         // LEFT
         if (IsReservedWord(Token, rwLEFT)) then
          begin
           JoinType := ajtLeftOuter;
           GetNextToken([tktReservedWord], 30171, ErrorGUnexpectedToken);
           Result := True;
          end
         else
         // RIGHT
         if (IsReservedWord(Token, rwRIGHT)) then
          begin
           JoinType := ajtRightOuter;
           GetNextToken([tktReservedWord], 30172, ErrorGUnexpectedToken);
           Result := True;
          end
         else
         // FULL
         if (IsReservedWord(Token, rwFULL)) then
          begin
           JoinType := ajtFullOuter;
           GetNextToken([tktReservedWord], 30173, ErrorGUnexpectedToken);
           Result := True;
          end;
         // [OUTER]
         if ((Result) and (IsReservedWord(Token, rwOUTER))) then
          begin
           GetNextToken([tktReservedWord], 30174, ErrorGUnexpectedToken);
          end;
        end;// outer join
      end; // non-cross join

     // next token - JOIN?
     if (not IsReservedWord(Token, rwJOIN)) then
      if (Result) then
       raise EABSException.Create(30175, ErrorGOtherTokenExpected,
                 ['JOIN', Token.Text, Token.LineNum, Token.ColumnNum])
      else
       exit; // no joins
     GetNextToken;

     // get right table ref
     RightTblRef := TABSTableReference.Create;
     if not ParseTableReference(RightTblRef) then
      raise EABSException.Create(30176, ErrorGTableNameExpected,
                       [Token.Text, Token.LineNum, Token.ColumnNum]);

     // <join condition> | <named columns join>
     Fields := nil;
     SearchCondition := nil;
     if not ParseJoinCondition(SearchCondition) then
       ParseNamedColumnsJoin(Fields);

     // update tree to make join
     tblRef.MakeJoin(RightTblRef, JoinType, IsNatural, Fields, SearchCondition)
  until (not(IsReservedWord(Token, rwCROSS) or
             IsReservedWord(Token, rwNATURAL) or
             IsReservedWord(Token, rwINNER) or
             IsReservedWord(Token, rwLEFT) or
             IsReservedWord(Token, rwRIGHT) or
             IsReservedWord(Token, rwFULL) or
             IsReservedWord(Token, rwOUTER) or
             IsReservedWord(Token, rwJOIN)));
end;// ParseJoin


//------------------------------------------------------------------------------
// Subquery: SELECT FROM (SELECT ...)
//------------------------------------------------------------------------------
function TABSSQLSelect.ParseFromSubquery(tblRef: TABSTableReference): Boolean;
var
  tRef: TABSTableReference;
begin
  // first table in joins chain?
  if (tblRef = nil) then
    begin
     // add new tables list item
     inc(FromTablesCount);
     SetLength(FromTables, FromTablesCount);
     FromTables[FromTablesCount-1] := TABSTableReference.Create;
     tRef := FromTables[FromTablesCount-1];
    end
  else
    tRef := tblRef;
  tRef.MakeSubQuery(LLex, LQuery);
  GetCurrentToken;
  Result := True;
end;// ParseFromSubquery


//------------------------------------------------------------------------------
// [MEMORY] <table name> [ [ AS ] <correlation name> ...
//------------------------------------------------------------------------------
function TABSSQLSelect.ParseTableReference(tblRef: TABSTableReference): Boolean;


  procedure ExtractTableNameAndDatabaseName(Text: string; var TableName: string; DatabaseName: string);
  var
    P, P1: PChar;
  begin
    P := PChar(Text);
    if P^ = '"' then
    begin
      DatabaseName := AnsiExtractQuotedStr(P, '"');
      P := CharNext(P);
      P1 := PChar(Text);
      SetString(TableName, P, P1 + Length(Text) - P);
    end
    else
      TableName := Text;
    (*SL := TStringList.Create;
    try
      Text
      SL.CommaText :=  Text;
      if SL.Count = 1 then
        TableName := Text
      else if SL.Count = 2 then
      begin
        TableName := SL[1];
        DatabaseName := SL[0];
      end;
    finally
     SL.Free;
    end;*)
  end;


var
  DatabaseName, TableName, Pseudonym, Password: string;
  TableType: TABSTableType;
  InMemory: Boolean;
  tRef: TABSTableReference;
begin
 Result := False;
 // handle [MEMORY] table1 | "database 1".table1
 if (Token.TokenType in [tktString, tktParameter, tktReservedWord, tktQuotedString,tktBracketedString]) then
  begin
   if (IsReservedWord(Token, rwSELECT)) then
     Result := ParseFromSubquery(tblRef)
   else
     begin
       Result := True;
       if (IsReservedWord(Token, rwMEMORY)) then
        begin
          InMemory := True;
          GetNextToken;
        end
       else
         InMemory := TABSDataSet(LQuery).InMemory;
       DatabaseName := '';

       // table_name
       if (Token.TokenType <> tktParameter) then
         TableName := Token.Text
       else begin
         // 5.05 - fix: parse database name from parameter value
         ExtractTableNameAndDatabaseName(string(Token.ParamValue.AsAnsiString), TableName, DatabaseName);
         // TableName := Token.ParamValue.AsString;
       end;


       // pseudonym is not specified yet
       Pseudonym := '';
       // password too
       Password := '';
       // tabletype is ettTable by default
       TableType := attTable;
       // look at next token
       if (GetNextToken) then
        begin
         // db.table?
         if (Token.TokenType = tktDot) then
          begin
           DatabaseName := TableName;
           // get table name
           GetNextToken;
           if (Token.TokenType <> tktParameter) then
             TableName := Token.Text
           else
             TableName := string(Token.ParamValue.AsAnsiString);
           // next token
           GetNextToken;
          end;

          CheckPseudonym(Pseudonym);

          // check password
          if (IsReservedWord(Token, rwPASSWORD)) then
           begin
            // get next token 'password'
            GetNextToken([tktQuotedString, tktParameter], 30178, ErrorGQuotedPasswordDataExpected);
            if (Token.TokenType <> tktParameter) then
              Password := Token.Text
            else
              Password := string(Token.ParamValue.AsAnsiString);
            // get next token
            GetNextToken;
           end;
        end;
       // first table in joins chain?
       if (tblRef = nil) then
        begin
         // add new tables list item
         inc(FromTablesCount);
         SetLength(FromTables, FromTablesCount);
         FromTables[FromTablesCount-1] := TABSTableReference.Create;
         tRef := FromTables[FromTablesCount-1];
        end
       else
        tRef := tblRef;

       // store TableType
       tRef.TableType := TableType;
       // store DatabaseName
       tRef.DatabaseName := DatabaseName;
       // store TableName
       tRef.TableName := TableName;
       // store Pseudonym
       tRef.Pseudonym := Pseudonym;
       // store Password
       tRef.Password := Password;
       // store [MEMORY]
       tRef.InMemory := InMemory;
     end;
  end
 else
  // (<table reference>)
  if (Token.TokenType = tktLeftParenthesis) then
   begin
    // skip '('
    GetNextToken;
    // get table reference
    Result := ParseTableReference(tblRef);
    // get its pointer
    if (tblRef = nil) then
     tRef := FromTables[FromTablesCount-1]
    else
     tRef := tblRef;

    if not Result then
     raise EABSException.Create(30179, ErrorGFieldNameExpected,
                      [Token.Text, Token.LineNum, Token.ColumnNum]);
    // skip ')'
    if (Token.TokenType = tktRightParenthesis) then
       GetNextToken;

    // check if "(table) AS T1"
    CheckPseudonym(Pseudonym);
    tRef.Pseudonym := Pseudonym;
   end;

 // join?
 if (Result) then
   ParseJoin(tRef);
end;// ParseTableReference


//------------------------------------------------------------------------------
// FROM ...
//------------------------------------------------------------------------------
function TABSSQLSelect.ParseFromClause: Boolean;
begin
 if (IsReservedWord(Token, rwFROM)) then
  begin
    // skip FROM token
    GetNextToken([tktString, tktParameter, tktReservedWord, tktQuotedString, tktBracketedString, tktLeftParenthesis],
                 30180, ErrorGTableNameExpected);
    Result := True;
    // get TableName
    if (ParseTableReference) then
     // get all tables
     repeat
        // end of sql command?
        if (Token.TokenType <> tktComma) then
          break
        else
          // skip ',' token
          GetNextToken([tktString, tktParameter, tktReservedWord, tktQuotedString, tktBracketedString, tktLeftParenthesis],
                       30181, ErrorGTableNameExpected);
       // get next table reference
       if (not ParseTableReference) then
        raise EABSException.Create(30182, ErrorGTableNameExpected,
                  [Token.Text, Token.LineNum, Token.ColumnNum]);
     until False
    else
      raise EABSException.Create(30183, ErrorGTableNameExpected,
                  [Token.Text, Token.LineNum, Token.ColumnNum]);
  end
 else
   raise EABSException.Create(30184, ErrorGOtherTokenExpected,
                ['FROM', Token.Text, Token.LineNum, Token.ColumnNum]);
end;// ParseFromClause


//------------------------------------------------------------------------------
// WHERE ...
//------------------------------------------------------------------------------
function TABSSQLSelect.ParseWhereClause: Boolean;
begin
 Result := False;
 if (IsReservedWord(Token, rwWHERE)) then
  begin
    Result := True;
    // skip WHERE token
    GetNextToken;
    SearchCondition := TABSExpression.Create;
    SearchCondition.InMemory := TABSQuery(LQuery).InMemory;
    SearchCondition.ParseForBooleanExpression(LQuery, LLex);

    if (not AllFields) then
     SearchCondition.ReplacePseudonyms(SelectList);
    // get current (not handled) token
    GetCurrentToken;
  end;
end;// ParseWhereClause


//------------------------------------------------------------------------------
// GROUP BY ...
//------------------------------------------------------------------------------
function TABSSQLSelect.ParseGroupByClause: Boolean;
begin
 if (IsReservedWord(Token, rwGROUP)) then
  begin
    Result := True;
    // skip GROUP token
    GetNextToken;
    // 'BY' ?
    if (not IsReservedWord(Token, rwBY)) then
     raise EABSException.Create(30185, ErrorGOtherTokenExpected,
                   ['BY', Token.Text, Token.LineNum, Token.ColumnNum]);
    // skip BY token
    GetNextToken;

    // parse fields list
    ParseFieldList(GroupByFields);
  end
 else
  Result := False;
end;// ParseGroupByClause


//------------------------------------------------------------------------------
// HAVING ...
//------------------------------------------------------------------------------
function TABSSQLSelect.ParseHavingClause: Boolean;
begin
 Result := False;
 if (IsReservedWord(Token, rwHAVING)) then
  begin
    Result := True;
    // skip HAVING token
    GetNextToken;
    HavingCondition := TABSExpression.Create;
    HavingCondition.ParseForBooleanExpression(LQuery, LLex);
    if (not AllFields) then
     HavingCondition.ReplacePseudonyms(SelectList);
    // get current (not handled) token
    GetCurrentToken;
  end;
end;// ParseHavingClause


//------------------------------------------------------------------------------
// parse query
//------------------------------------------------------------------------------
procedure TABSSQLSelect.Parse;
var
  State: integer;
  bOk: Boolean;
begin
   // get first token (for new query) or current token (for subquery)
   //bOk := LLex.GetCurrentToken(Token);
   bOk := GetCurrentToken;

   if (not bOk) then
      raise EABSException.Create(30155, ErrorGBlankSqlCommand);

   state := 0;
   repeat
      // states switch (depends on a current part of query)
      case state of
       0: // SELECT
          ParseSelectToken;
       1: // DISTINCT | ALL ?
          ParseSetQuantifier;
       2: // TOP n ?
          ParseTopOperator;
       3: // * | <select sublist>
          ParseSelectList;
       4: // <into ...>
          ParseInto;
       5: // <from clause>
          ParseFromClause;
       6: // <where clause>
          ParseWhereClause;
       7: // <group by clause>
          ParseGroupByClause;
       8: // <having clause>
          ParseHavingClause;
       9: // <order by clause>
          ParseOrderByClause;
       else
        break;
      end;// case
      inc(state);
   until (false);

end;// Parse


//------------------------------------------------------------------------------
// gets default database name
//------------------------------------------------------------------------------
function TABSSQLSelect.GetDefaultDatabaseName: string;
begin
  //raise EABSException.Create(30192, ErrorGNotImplementedYet);
  Result := TABSDataset(ResultDataset).DatabaseName;
end;// GetDefaultDatabaseName


//------------------------------------------------------------------------------
// gets DisableTempFiles value
//------------------------------------------------------------------------------
function TABSSQLSelect.GetDisableTempFiles: boolean;
begin
  Result := False;
  if (TABSDataset(ResultDataset).Database <> nil) then
    Result := TABSDataset(ResultDataset).Database.DisableTempFiles;
end;// GetDisableTempFiles


//------------------------------------------------------------------------------
// creates and adjusts table AO
//------------------------------------------------------------------------------
function TABSSQLSelect.CreateTableAO(var TableRef: TABSTableReference): TABSAO;
var
  DB: TObject;
  Table: TDataset;
begin
  CreateDbAndTableComponents(LQuery, TableRef.DatabaseName, TableRef.TableName,
    TableRef.Password, TableRef.InMemory or TABSDataset(ResultDataset).InMemory,
    Table, DB);
  TableRef.ExternalDBToFree := DB;
  Result := TABSAOTable.Create(TableRef.TableName,
                               TableRef.Pseudonym,
                               DB, Table);
end;// CreateTableAO


//------------------------------------------------------------------------------
// creates and adjusts joined table AO
//------------------------------------------------------------------------------
function TABSSQLSelect.CreateJoinedTableAO(var TableRef: TABSTableReference;
                       ParentQueryAO: TABSAO; ParentCursor: TABSCursor;
                       AllowNotAppliedConditions: Boolean): TABSAO;
var
 LeftAO, RightAO: TABSAO;
 FieldList1, FieldList2: TABSFields;
 i: integer;
 Item1, Item2: TABSSelectListItem;
 bOuterJoin: Boolean;
 JoinCondition: TABSExpression;

function RecursiveExtractJoinConditions(TableRef: TABSTableReference): integer;
begin
  if (TableRef.SearchCondition <> nil) then
     Result := TableRef.SearchCondition.ExtractJoinConditions(LeftAO, RightAO,
                                      FieldList1, FieldList2)
  else
   begin
     Result := 0;
     if (TableRef.TableType = attJoinedTable) then
      begin
       if (TableRef.LeftTable.SearchCondition <> nil) then
        Result := Result + TableRef.LeftTable.SearchCondition.ExtractJoinConditions(
                         LeftAO, RightAO, FieldList1, FieldList2);

       if (TableRef.RightTable.SearchCondition <> nil) then
        Result := Result + TableRef.RightTable.SearchCondition.ExtractJoinConditions(
                         LeftAO, RightAO, FieldList1, FieldList2);
      end;
   end;
end; // RecursiveExtractJoinConditions

begin
 // create joined tables
 LeftAO := CreateAO(TableRef.LeftTable, True, ParentQueryAO, ParentCursor, AllowNotAppliedConditions);
 RightAO := CreateAO(TableRef.RightTable, True, ParentQueryAO, ParentCursor, AllowNotAppliedConditions);

 bOuterJoin := (TableRef.JoinType in [ajtLeftOuter, ajtRightOuter, ajtFullOuter]);

 // prepare column lists
 FieldList1 := nil;
 FieldList2 := nil;
 try
 // column lists required?
 if ((not TableRef.NaturalJoin) and
     (TableRef.JoinType in [ajtInner, ajtLeftOuter, ajtRightOuter, ajtFullOuter])) then
  begin // build column lists
   // USING ?
   if (TableRef.UsingFields <> nil) then
    begin
      FieldList1 := TABSFields.Create;
      FieldList2 := TABSFields.Create;
      for i := 0 to TableRef.UsingFields.Count-1 do
       begin
        Item1.FieldName := TableRef.UsingFields.Strings[i];
        Item1.TableName := TableRef.LeftTable.TableName;
        Item2.FieldName := TableRef.UsingFields.Strings[i];
        Item2.TableName := TableRef.RightTable.TableName;
        FieldList1
        .Append(Item1);
        FieldList2.Append(Item2);
       end;
    end
   else
    begin
      FieldList1 := TABSFields.Create;
      FieldList2 := TABSFields.Create;
      // extract "T1.f1=T2.f2" from ON clause
      RecursiveExtractJoinConditions(TableRef);
      // extract "T1.f1=T2.f2" from WHERE clause
      if (not bOuterJoin) then
        if (SearchCondition <> nil) then
         SearchCondition.ExtractJoinConditions(LeftAO, RightAO,
                                         FieldList1, FieldList2);
    end
  end;

  // the rest of ON conditions, like 'F1=const' or 'const1=const2'
  // we should keep them separately from filter for outer joins
  JoinCondition := nil;
  if (bOuterJoin) and (TableRef.SearchCondition <> nil) then
    if (not TableRef.SearchCondition.IsEmpty) then
      begin
        JoinCondition := TableRef.SearchCondition;
        TableRef.SearchCondition := nil; // we will free it in JoinAO.destroy
      end;

  // create join AO
  Result := TABSAOJoin.Create(LeftAO, RightAO, TableRef.JoinType,
                              TableRef.NaturalJoin,
                              FieldList1, FieldList2, JoinCondition);

 // set as filter the rest of ON condition
 if (not bOuterJoin) then
   if (TableRef.SearchCondition <> nil) then
     begin
       TableRef.SearchCondition.ApplyFilterParts(Result, nil, nil);
       if (not TableRef.SearchCondition.IsEmpty) then
         begin
           Result.Free;
           Result := nil;
           raise EABSException.Create(20270, ErrorANotApplicableOnCondition);
         end;
     end;

 finally
  if (FieldList1 <> nil) then
   FieldList1.Free;
  if (FieldList2 <> nil) then
   FieldList2.Free;
 end;
end;// CreateJoinedTableAO


//------------------------------------------------------------------------------
// creates and adjusts subquery AO
//------------------------------------------------------------------------------
function TABSSQLSelect.CreateSubQueryAO(var TableRef: TABSTableReference; RequestLive: Boolean; 
                       ParentQueryAO: TABSAO; ParentCursor: TABSCursor;
                       AllowNotAppliedConditions: Boolean): TABSAO;
begin
  Result := TableRef.SubQuery.BuildAOTree(LQuery, True, ParentQueryAO, ParentCursor,
                                          AllowNotAppliedConditions);
  if (Result <> nil) then
    begin
      FSubqueryHasNotAppliedConditions := not TableRef.SubQuery.AllConditionsApplied;
      Result.TableAlias := TableRef.Pseudonym;
    end;
end;// CreateSubQueryAO


//------------------------------------------------------------------------------
// creates and adjusts AO
//------------------------------------------------------------------------------
function TABSSQLSelect.CreateAO(var TableRef: TABSTableReference;RequestLive: Boolean; 
                      ParentQueryAO: TABSAO; ParentCursor: TABSCursor;
                      AllowNotAppliedConditions: Boolean): TABSAO;
begin
 case TableRef.TableType of
  attTable:
    Result := CreateTableAO(TableRef);
  attJoinedTable:
    Result := CreateJoinedTableAO(TableRef, ParentQueryAO, ParentCursor, AllowNotAppliedConditions);
  attSubquery:
    Result := CreateSubQueryAO(TableRef, RequestLive, ParentQueryAO, ParentCursor, AllowNotAppliedConditions);
  else
    Result := nil;
 end;
end;// CreateAO


//------------------------------------------------------------------------------
// builds one-table AO
//------------------------------------------------------------------------------
function TABSSQLSelect.BuildOneTableTree(RequestLive: Boolean; ParentQueryAO: TABSAO;
         ParentCursor: TABSCursor; AllowNotAppliedConditions: Boolean): TABSAO;
begin
  // create AO
  Result := CreateAO(FromTables[0], RequestLive, ParentQueryAO, ParentCursor, AllowNotAppliedConditions);
end;// BuildOneTableTree


//------------------------------------------------------------------------------
// builds multi-tables AO tree
//------------------------------------------------------------------------------
function TABSSQLSelect.BuildMultiTablesTree(RequestLive: Boolean; ParentQueryAO: TABSAO;
          ParentCursor: TABSCursor; AllowNotAppliedConditions: Boolean): TABSAO;
var
  i, JoinConditionCount: integer;
  RightAO: TABSAO;
  JoinType: TABSJoinType;
  FieldList1, FieldList2: TABSFields;
begin
 Result := CreateAO(FromTables[0], RequestLive, ParentQueryAO, ParentCursor, AllowNotAppliedConditions);
 // create other joins
 for i := 1 to FromTablesCount-1 do
  begin
   // init column lists
   FieldList1 := nil;
   FieldList2 := nil;
   try
    RightAO := CreateAO(FromTables[i], RequestLive, ParentQueryAO, ParentCursor, AllowNotAppliedConditions);
    // try to find join conditions in WHERE clause
    if (SearchCondition <> nil) then
     begin
       FieldList1 := TABSFields.Create;
       FieldList2 := TABSFields.Create;
       // try to extract conditions
       JoinConditionCount := SearchCondition.ExtractJoinConditions(Result, RightAO,
                                             FieldList1, FieldList2);
     end
    else
     JoinConditionCount := 0;

    if (JoinConditionCount = 0) then
     begin
      JoinType := ajtCross;
      if (FieldList1 <> nil) then
       FieldList1.Free;
      if (FieldList2 <> nil) then
       FieldList2.Free;
      FieldList1 := nil;
      FieldList2 := nil;
     end
    else
     JoinType := ajtInner;

    // create join AO
    Result := TABSAOJoin.Create(Result, RightAO, JoinType, False,
                                FieldList1, FieldList2, nil);
   finally
     if (FieldList1 <> nil) then
      FieldList1.Free;
     if (FieldList2 <> nil) then
      FieldList2.Free;
   end;
  end;
end;// BuildMultiTablesTree









////////////////////////////////////////////////////////////////////////////////
//
// TABSSQLCommand
//
////////////////////////////////////////////////////////////////////////////////


//------------------------------------------------------------------------------
// Constructor
//------------------------------------------------------------------------------
constructor TABSSQLCommand.Create(Lexer: TABSLexer; Query: TDataSet);
begin
  IntoMemory := False;
  LLex := Lexer;
  LQuery := Query;
  RowsAffected := 0;
  // parse with first token
  Parse;
end;//Create


//------------------------------------------------------------------------------
// parses list of columns (without table name): field1, field2, ...
//------------------------------------------------------------------------------
procedure TABSSQLCommand.ParseColumnList(var Fields: TStringList);

function ParseColumnName(var ColumnName: string): Boolean;
begin
 Result := False;
 if (Token.TokenType in [tktString, tktQuotedString, tktBracketedString]) then
  begin
   ColumnName := Token.Text;
   GetNextToken;
   Result := True;
  end;
end;

var
  s: string;

begin
      // get ColumnName
      if (ParseColumnName(s)) then
       // all columns
       repeat
        // create list if necessary
        if (Fields = nil) then
         Fields := TStringList.Create;
        // add column name
        Fields.Add(s);
        // ',' ?
        if (Token.TokenType <> tktComma) then
           break
        else
          // skip ',' token
          GetNextToken([tktString, tktQuotedString, tktBracketedString],10406,
            ErrorLUnexpectedToken);
        // get next field
        if (not ParseColumnName(s)) then
         raise EABSException.Create(10407,ErrorLFieldNameExpected,
          [Token.Text, Token.LineNum, Token.ColumnNum]);
       until False
      else
       raise EABSException.Create(10408, ErrorLFieldNameExpected,
        [Token.Text, Token.LineNum, Token.ColumnNum]);
end; // ParseColumnList


//------------------------------------------------------------------------------
// parses list of fields:  table.field1, field2, ..
//------------------------------------------------------------------------------
procedure TABSSQLCommand.ParseFieldList(var Fields: TABSFields);

function ParseFieldSpecification(var Fields: TABSFields): Boolean;
var
   item: TABSSelectListItem;
   TableName, FieldName: string;
   Expr: TABSExpression;
begin
 Result := True;
 // parse field as expression
 Expr := TABSExpression.Create;
 try
  Expr.ParseForValueExpression(LQuery, LLex);
  // only field is allowed
  if (not Expr.IsField) then
   raise EABSException.Create(10409, ErrorLFieldNameExpected,
            [Token.Text, Token.LineNum, Token.ColumnNum]);
  GetCurrentToken;
  // extract field name, table name
  Expr.GetFieldInfo(TableName, FieldName);
  // add item
  item.TableName := TableName;
  item.FieldName := FieldName;
  item.IsExpression := False;
  item.AllFields := false;
  item.Pseudonym := '';
  item.ValueExpr := nil;
  Fields.Append(item);
 finally
  Expr.Free;
 end;

end;

begin
    // parse field specification list
    if (ParseFieldSpecification(Fields)) then
     // get all specs
     repeat
        // end of sql command?
        if (Token.TokenType <> tktComma) then
          break
        else
          // skip ',' token
          GetNextToken([tktString, tktQuotedString, tktBracketedString,
                        tktReservedWord],10410,ErrorLUnexpectedToken);
       // get next spec
       if (not ParseFieldSpecification(Fields)) then
        raise EABSException.Create(10411, ErrorLFieldNameExpected,
                  [Token.Text, Token.LineNum, Token.ColumnNum]);
     until False
    else
      raise EABSException.Create(10412, ErrorLFieldNameExpected,
          [Token.Text, Token.LineNum, Token.ColumnNum]);
end;// ParseFieldList


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


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


//------------------------------------------------------------------------------
// get next token
//------------------------------------------------------------------------------
function TABSSQLCommand.GetNextToken(PermittedTypes: TTokenTypes;
  NativeErrorCode: integer; ErrorText: string): Boolean;
begin
  // get next token
  Result := LLex.GetNextToken(Token);
  if (not Result) then
   if (NativeErrorCode <> -1) then
      raise EABSException.Create(30161, ErrorGUnexpectedEndOfCommand,
                                 [Token.LineNum, Token.ColumnNum]);
  // check token type
  if (Result) then
   if ((PermittedTypes <> []) and (NativeErrorCode <> -1)) then
    if not (Token.TokenType in PermittedTypes) then
      raise EABSException.Create(NativeErrorCode, ErrorText,
                                 [Token.Text, Token.LineNum, Token.ColumnNum])
      else
         Result := False;
end;//GetNextToken


//------------------------------------------------------------------------------
// ParseTableNameToken
//------------------------------------------------------------------------------
procedure TABSSQLCommand.ParseTableNameToken(var DatabaseFileName, TableName, TableAlias, Password: string; var InMemory: Boolean);
begin
  DatabaseFileName := '';
  Password := '';
  InMemory := false;

  if not (Token.TokenType in [tktString, tktReservedWord, tktBracketedString, tktQuotedString, tktParameter]) then
    raise EABSException.Create(30230, ErrorGTableNameExpected,
                        [Token.Text, Token.LineNum, Token.ColumnNum]);

  // MEMORY
  if IsReservedWord(Token, rwMEMORY) then
   begin
    InMemory := true;
    GetNextToken([tktReservedWord,tktString,tktQuotedString,tktBracketedString,tktParameter],
                  30231, ErrorGTableNameExpected);
   end;

  // table_name
  if (Token.TokenType <> tktParameter) then
    TableName := Token.Text
  else
    TableName := string(Token.ParamValue.AsAnsiString);

  // look at next token
  if (GetNextToken) then
    begin
     // db.table?
     if (Token.TokenType = tktDot) then
      begin
       DatabaseFileName := TableName;
       // get table name
       GetNextToken;
       if (Token.TokenType <> tktParameter) then
         TableName := Token.Text
       else
         TableName := string(Token.ParamValue.AsAnsiString);
       // next token
       GetNextToken;
      end;
    end;

  if not (CheckPseudonym(TableAlias)) then
    TableAlias := '';

  if (Token.ReservedWord = rwPASSWORD) then
    begin
      GetNextToken([tktQuotedString, tktParameter], 30238, ErrorGQuotedPasswordDataExpected);
      if (Token.TokenType <> tktParameter) then
        Password := Token.Text
      else
        Password := string(Token.ParamValue.AsAnsiString);
      GetNextToken;
    end;
end;// ParseTableNameToken


//------------------------------------------------------------------------------
// CreateDbAndTableComponents
//------------------------------------------------------------------------------
procedure TABSSQLCommand.CreateDbAndTableComponents(
                      Query: TDataSet;
                      DatabaseFileName, TableName, Password: string;
                      InMemory: Boolean;
                      var Table: TDataset;
                      var DB: TObject);
begin
  DB := TABSDatabase.Create(nil);
  if ((query as TABSQuery).Database <> nil) then
    begin
      TABSDatabase(DB).MultiUser := (query as TABSQuery).Database.MultiUser;
      TABSDatabase(DB).DisableTempFiles := (query as TABSQuery).Database.DisableTempFiles;
    end;
  TABSDatabase(DB).DatabaseFileName := DatabaseFileName;
  TABSDatabase(DB).SessionName := (query as TABSQuery).SessionName;
  repeat
    try
      TABSDatabase(DB).DatabaseName := GetTemporaryName('TEMP_DB');
      break;
    except
    end;
  until (false);
  TABSDatabase(DB).Password := Password;
  TABSDatabase(DB).SilentMode := True;

  Table := TABSTable.Create(nil);
  TABSTable(Table).SessionName := (query as TABSQuery).SessionName;
  if (DatabaseFileName = '') then
    begin
      TABSTable(Table).DatabaseName := (query as TABSQuery).DatabaseName;
    end
  else
    TABSTable(Table).DatabaseName := TABSDatabase(DB).DatabaseName;
  TABSTable(Table).TableName := TableName;
  TABSTable(Table).InMemory := InMemory or TABSDataset(query).InMemory;
end;// CreateDbAndTableComponents


//------------------------------------------------------------------------------
// FreeDbAndTableComponents
//------------------------------------------------------------------------------
procedure TABSSQLCommand.FreeDbAndTableComponents(Table: TDataset; DB: TObject);
begin
  if (Table <> nil) then
    Table.Free;
  if (DB <> nil) then
    DB.Free;
end;// FreeDbAndTableComponents


//------------------------------------------------------------------------------
// get result cursor
//------------------------------------------------------------------------------
function TABSSQLCommand.GetResultCursor: TABSCursor;
begin
  Result := nil;
end;//GetResultCursor


//------------------------------------------------------------------------------
// gets result dataset
//------------------------------------------------------------------------------
function TABSSQLCommand.GetResultDataset: TDataset;
begin
  Result := nil;
end;// GetResultDataset


//------------------------------------------------------------------------------
// CheckPseudonym
//------------------------------------------------------------------------------
function TABSSQLCommand.CheckPseudonym(var Pseudonym: string): Boolean;
begin
  Result := False;
  // check pseudonym - correlation name (Table1 AS t1)
  if (IsReservedWord(Token, rwAS)) then
        begin
         // get next token (t1?)
         GetNextToken([tktString, tktParameter, tktQuotedString, tktBracketedString],
                       30177, ErrorGFieldPseudonymExpected);
         // got pseudonym
         if (Token.TokenType <> tktParameter) then
           Pseudonym := Token.Text
         else
           Pseudonym := string(Token.ParamValue.AsAnsiString);
         // get next token
         GetNextToken;
         Result := True;
        end
  else
    if (Token.TokenType in [tktString, tktBracketedString, tktParameter]) then
        begin
         // got pseudonym
         if (Token.TokenType <> tktParameter) then
           Pseudonym := Token.Text
         else
           Pseudonym := string(Token.ParamValue.AsAnsiString);
         // get next token
         GetNextToken;
         Result := True;
        end;
end;



////////////////////////////////////////////////////////////////////////////////
//
// TABSQueryExprNode
//
////////////////////////////////////////////////////////////////////////////////


//------------------------------------------------------------------------------
// Constructor
//------------------------------------------------------------------------------
constructor TABSQueryExprNode.Create;
begin
  NodeType := qetSelect;
  Left := nil;
  Right := nil;
  All := False;
  Corresponding := False;
  CorrespondingFields := nil;
  SelectCommand := nil;
end;// Create


//------------------------------------------------------------------------------
// Constructor
//------------------------------------------------------------------------------
constructor TABSQueryExprNode.Create(Src: TABSQueryExprNode);
begin
  NodeType := Src.NodeType;
  Left := Src.Left;
  Right := Src.Right;
  All := Src.All;
  Corresponding := Src.Corresponding;
  CorrespondingFields := Src.CorrespondingFields;
  SelectCommand := Src.SelectCommand;
end;// Create


//------------------------------------------------------------------------------
// Destructor
//------------------------------------------------------------------------------
destructor TABSQueryExprNode.Destroy;
begin
 if (Left <> nil) then
  Left.Free;
 if (Right <> nil) then
  Right.Free;

 if (SelectCommand <> nil) then
  SelectCommand.Free;
end;// Destroy


//------------------------------------------------------------------------------
// AddNode
//------------------------------------------------------------------------------
procedure TABSQueryExprNode.AddNode(NewType: TABSQueryExprType;
  RightNode: TABSQueryExprNode; bAll, bCorresponding: Boolean;
  ColumnList: TStringList);
var
  LeftNode: TABSQueryExprNode;
begin
  // copy current node to left
  LeftNode := TABSQueryExprNode.Create(Self);

  // set childs
  Left := LeftNode;
  Right := RightNode;

  // set current data as: union | except | ...
  SelectCommand := nil;
  NodeType := NewType;
  CorrespondingFields := ColumnList;
  All := bAll;
  Corresponding := bCorresponding;
end;// AddNode


//------------------------------------------------------------------------------
// AllConditionsApplied
//------------------------------------------------------------------------------
function TABSQueryExprNode.AllConditionsApplied: Boolean;
begin
 if (NodeType = qetSelect) then
   Result := SelectCommand.AllConditionsApplied
 else
   Result := Left.AllConditionsApplied and
             Right.AllConditionsApplied;
end;// AllConditionsApplied


//------------------------------------------------------------------------------
// check that there is no INTO clause
//------------------------------------------------------------------------------
function TABSQueryExprNode.HasInto: Boolean;
begin
 if (NodeType = qetSelect) then
   Result := (SelectCommand.IntoTable <> '')
 else
   Result := Left.HasInto or Right.HasInto;
end;// HasInto


////////////////////////////////////////////////////////////////////////////////
//
// TABSSQLUnion
//
////////////////////////////////////////////////////////////////////////////////


//------------------------------------------------------------------------------
// Constructor
//------------------------------------------------------------------------------
constructor TABSSQLUnion.Create(Lexer: TABSLexer; Query: TDataSet);
begin
 FRootNode := nil; // not parsed yet
 inherited Create(Lexer, Query);
end;// Create


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


//------------------------------------------------------------------------------
// parses [ALL]
//------------------------------------------------------------------------------
function TABSSQLUnion.ParseAll: Boolean;
begin
  Result := IsReservedWord(Token, rwALL);
  // skip token
  if (Result) then
   GetNextToken;
end;//ParseAll


//------------------------------------------------------------------------------
// parses [ <corresponding spec> ]
//------------------------------------------------------------------------------
function TABSSQLUnion.ParseCorrespondingSpec(var ColumnsList: TStringList): Boolean;
begin
  ColumnsList := nil;
  Result := IsReservedWord(Token, rwCORRESPONDING);
  if (Result) then
   begin
    // skip token
    GetNextToken;
    // BY (<corresponding column list>)?
    if (IsReservedWord(Token, rwBY)) then
     begin
      // skip token
      GetNextToken;
      // '(' ?
      if (Token.TokenType = tktLeftParenthesis) then
       begin
        // skip '('
        GetNextToken;

        // parses fields list
        ParseColumnList(ColumnsList);

        // skip ')'
        if (Token.TokenType = tktRightParenthesis) then
          GetNextToken; // end of query is possible here
       end
      else
        raise EABSException.Create(30193, ErrorGOtherTokenExpected,
                    ['(', Token.Text, Token.LineNum, Token.ColumnNum]);
     end;
   end;
end;// ParseCorrespondingSpec


//------------------------------------------------------------------------------
// parses SELECT ...
//------------------------------------------------------------------------------
function TABSSQLUnion.ParseQuerySpecification: TABSQueryExprNode;
var
  SelectNode: TABSQueryExprNode;
begin
  if (IsReservedWord(Token, rwSELECT)) then
   begin
    SelectNode := TABSQueryExprNode.Create;
    SelectNode.NodeType := qetSelect;
    try
     SelectNode.SelectCommand := TABSSQLSelect.Create(LLex, LQuery);
     GetCurrentToken;
    except
     SelectNode.Free;
     raise;
    end;
    Result := SelectNode;
   end
  else
   raise EABSException.Create(30194, ErrorGOtherTokenExpected,
              ['SELECT', Token.Text, Token.LineNum, Token.ColumnNum]);
end;// ParseQuerySpecification


//------------------------------------------------------------------------------
// parses <query specification> | <table value constructor>  | <explicit table>
//------------------------------------------------------------------------------
function TABSSQLUnion.ParseSimpleTable: TABSQueryExprNode;
begin
  Result := ParseQuerySpecification;
end;// ParseSimpleTable


//------------------------------------------------------------------------------
// parses <simple table> |
// <left paren> <non-join query expression> <right paren>
//------------------------------------------------------------------------------
function TABSSQLUnion.ParseNonJoinQueryPrimary: TABSQueryExprNode;
begin
  // '(' ?
  if (Token.TokenType = tktLeftParenthesis) then
   begin
     // skip '('
     GetNextToken;

     // parses <query expression>
     Result := ParseQueryExpression;

     // skip ')'
     if (Token.TokenType = tktRightParenthesis) then
       GetNextToken; // end of query is possible here
   end
  else
   Result := ParseSimpleTable;
end;// ParseNonJoinQueryPrimary


//------------------------------------------------------------------------------
// parses <non-join query primary> |
// <query term> INTERSECT [ ALL ] [ <corresponding spec> ] <query primary>
//------------------------------------------------------------------------------
function TABSSQLUnion.ParseNonJoinQueryTerm: TABSQueryExprNode;
var
  RightNode: TABSQueryExprNode;
  bAll, bCorresponding: Boolean; // union or except
  ColumnList: TStringList; // corresponding spec
begin
  // parse <non-join query primary>
  Result := ParseNonJoinQueryPrimary;

  // intersects?
  while (IsReservedWord(Token, rwINTERSECT)) do
   begin
     // skip INTERSECT token
     GetNextToken;

     // [ALL]
     bAll := ParseAll;

     // [ <corresponding spec> ]
     bCorresponding := ParseCorrespondingSpec(ColumnList);

     // parse <non-join query primary>
     RightNode := ParseNonJoinQueryPrimary;

     // make Intersect node
     Result.AddNode(qetIntersect, RightNode, bAll, bCorresponding, ColumnList);
   end;
end;// ParseNonJoinQueryTerm


//------------------------------------------------------------------------------
// parses <non-join query term> |
// <query expression> UNION  [ ALL ] [ <corresponding spec> ] <query term> |
// <query expression> EXCEPT [ ALL ] [ <corresponding spec> ] <query term>
//------------------------------------------------------------------------------
function TABSSQLUnion.ParseNonJoinQueryExpression: TABSQueryExprNode;
var
  RightNode: TABSQueryExprNode;
  bUnion, bAll, bCorresponding: Boolean; // union or except
  ColumnList: TStringList; // corresponding spec
  NewType: TABSQueryExprType;
begin
  // parse <non-join query term>
  Result := ParseNonJoinQueryTerm;

  // unions | excepts?
  while (IsReservedWord(Token, rwUNION) or
         IsReservedWord(Token, rwMINUS) or
         IsReservedWord(Token, rwEXCEPT)) do
   begin
     // union or except?
     bUnion := IsReservedWord(Token, rwUNION);
     // skip UNION | EXCEPT token
     GetNextToken;
     if (bUnion) then
      NewType := qetUnion
     else
      NewType := qetExcept;

     // [ALL]
     bAll := ParseAll;

     // [ <corresponding spec> ]
     bCorresponding := ParseCorrespondingSpec(ColumnList);

     // parse <non-join query term>
     RightNode := ParseNonJoinQueryTerm;

     // make union | except node
     Result.AddNode(NewType, RightNode, bAll, bCorresponding, ColumnList);
   end;
end;// ParseNonJoinQueryExpression


//------------------------------------------------------------------------------
// parses <non-join query expression>  | <joined table>
//------------------------------------------------------------------------------
function TABSSQLUnion.ParseQueryExpression: TABSQueryExprNode;
begin
  Result := ParseNonJoinQueryExpression;
end;// ParseQueryExpression


//------------------------------------------------------------------------------
// parses query
//------------------------------------------------------------------------------
procedure TABSSQLUnion.Parse;
var
  I: Integer;
begin
  GetCurrentToken;
  FRootNode := ParseQueryExpression;
  ParseOrderByClause;
  // move Order By specifications from right select to Union
  if (OrderBySpecsCount = 0) and Assigned(FRootNode.Right) then
    if (FRootNode.Right.NodeType = qetSelect) then
    begin
      OrderBySpecsCount := FRootNode.Right.SelectCommand.OrderBySpecsCount;
      SetLength(OrderBySpecs, OrderBySpecsCount);
      for I := 0 to  FRootNode.Right.SelectCommand.OrderBySpecsCount - 1 do
      begin
        OrderBySpecs[I] := FRootNode.Right.SelectCommand.OrderBySpecs[I];
      end;
      FRootNode.Right.SelectCommand.OrderBySpecsCount := 0;
      SetLength(FRootNode.Right.SelectCommand.OrderBySpecs, 0);
    end;
  // store INTO parameters
  if (FRootNode.NodeType = qetSelect) then
   begin
    IntoTable := FRootNode.SelectCommand.IntoTable;
    IntoDatabase := FRootNode.SelectCommand.IntoDatabase;
    IntoMemory := FRootNode.SelectCommand.IntoMemory;
    IntoImmediate := FRootNode.SelectCommand.IntoImmediate;
   end
  else
    if (FRootNode.HasInto) then
      raise EABSException.Create(20272, ErrorAIntoClauseInUnionSelect);
end;// Parse


//------------------------------------------------------------------------------
// check if correlated subquery apllied
//------------------------------------------------------------------------------
function TABSSQLUnion.AllConditionsApplied: Boolean;
begin
  Result := FRootNode.AllConditionsApplied;
end;// AllConditionsApplied


//------------------------------------------------------------------------------
// builds AO
//------------------------------------------------------------------------------
function TABSSQLUnion.BuildAO(Node: TABSQueryExprNode; RequestLive: Boolean;
                         ParentQueryAO: TABSAO; ParentCursor: TABSCursor;
                         AllowNotAppliedConditions: Boolean): TABSAO;
var
  LeftAO, RightAO: TABSAO;
  UnionType: TABSUnionType;
  CorrespondingFields: TABSFields;
  Item: TABSSelectListItem;
  i: integer;
begin
 if (Node.NodeType = qetSelect) then
   Result := Node.SelectCommand.BuildAOTree(ResultDataset, RequestLive, ParentQueryAO,
                                        ParentCursor, AllowNotAppliedConditions)
 else
  begin
    // union | except | intersect
    LeftAO := BuildAO(Node.Left, True, ParentQueryAO, ParentCursor, AllowNotAppliedConditions);
    RightAO := BuildAO(Node.Right, True, ParentQueryAO, ParentCursor, AllowNotAppliedConditions);

    UnionType := autUnion;
    case (Node.NodeType) of
      qetUnion:
         UnionType := autUnion;
      qetExcept:
         UnionType := autExcept;
      qetIntersect:
         UnionType := autIntersect;
    end;

    if (Node.CorrespondingFields <> nil) then
     begin
      CorrespondingFields := TABSFields.Create;
      // fill fields list
      Item.TableName := '';
      Item.Pseudonym := '';
      Item.AllFields := False;
      for i := 0 to Node.CorrespondingFields.Count-1 do
       begin
        Item.FieldName := Node.CorrespondingFields.Strings[i];
        CorrespondingFields.Append(Item);
       end
     end
    else
     CorrespondingFields := nil;
    try
     // create AO
     Result := TABSAOUnion.Create(LeftAO, RightAO, UnionType,
                                  ParentQueryAO, ParentCursor,
                                  Node.Corresponding, not Node.All,
                                  CorrespondingFields);
    finally
     if (CorrespondingFields <> nil) then
      CorrespondingFields.Free;
    end;
  end;
end;// BuildAO


//------------------------------------------------------------------------------
// builds AO tree
//------------------------------------------------------------------------------
function TABSSQLUnion.BuildAOTree(query: TDataset; RequestLive: Boolean; ParentQueryAO: TABSAO;
          ParentCursor: TABSCursor; AllowNotAppliedConditions: Boolean): TABSAO;
begin
  ResultDataset := query;
  Result := BuildAO(FRootNode, RequestLive, ParentQueryAO, ParentCursor, AllowNotAppliedConditions);
  ApplyOrderBy(Result);
end;// BuildAOTree




////////////////////////////////////////////////////////////////////////////////
//
// TABSSQLCursorCommand
//
////////////////////////////////////////////////////////////////////////////////

//------------------------------------------------------------------------------
// OpenAndLockTablesBeforeSelect
//------------------------------------------------------------------------------
function TABSSQLCursorCommand.OpenAndLockTablesBeforeSelect: Boolean;
var
  i: Integer;
begin
  Result := RootAO.OpenTables;
  if (Result) then
    for i := 0  to SelectTablesLockRetries-1 do
      begin
        Result := RootAO.LockTables;
        if (Result) then
          break;
        if (not Result) then
          if (not RootAO.UnlockTables) then
            raise EABSException.Create(20226, ErrorAFailedToUnlockTable);
        Sleep(SelectTablesLockDelay);
      end;
end;// OpenAndLockTablesBeforeSelect


//------------------------------------------------------------------------------
// UnlockTablesAfterSelect
//------------------------------------------------------------------------------
function TABSSQLCursorCommand.UnlockTablesAfterSelect: Boolean;
begin
  Result := RootAO.UnlockTables;
  if (not Result) then
     raise EABSException.Create(20227, ErrorAFailedToUnlockTable);
end;// UnlockTablesAfterSelect


//------------------------------------------------------------------------------
// <sort key> [ <collate clause> ] [ <ordering specification> ]
//------------------------------------------------------------------------------
function TABSSQLCursorCommand.ParseSortSpecification: Boolean;
begin
  Result := True;
  if (Token.TokenType in
       [tktString, tktBracketedString, tktReservedWord, tktQuotedString, tktInt, tktParameter]) then
   begin
     // new order spec
     Inc(OrderBySpecsCount);
     SetLength(OrderBySpecs, OrderBySpecsCount);
     if (Token.TokenType <> tktParameter) then
       OrderBySpecs[OrderBySpecsCount-1].ColumnName := AnsiString(Token.Text)
     else
       OrderBySpecs[OrderBySpecsCount-1].ColumnName := Token.ParamValue.AsAnsiString;

     // get next token
     GetNextToken;
     // "." ?
     if (Token.TokenType = tktDot) then
      begin
       // previous token was table name
       OrderBySpecs[OrderBySpecsCount-1].TableName :=
                      OrderBySpecs[OrderBySpecsCount-1].ColumnName;
       // get next token - column name
       GetNextToken([tktString, tktQuotedString, tktBracketedString, tktReservedWord, tktParameter],
                    30186, ErrorGUnexpectedToken);
       if (Token.TokenType <> tktParameter) then
         OrderBySpecs[OrderBySpecsCount-1].ColumnName := AnsiString(Token.Text)
       else
         OrderBySpecs[OrderBySpecsCount-1].ColumnName := Token.ParamValue.AsAnsiString;
       // get next token
       GetNextToken;
      end;
     // ASC | DESC ?
     if (IsReservedWord(Token, rwASC)) then
      begin
       OrderBySpecs[OrderBySpecsCount-1].Descending := False;
       // skip ASC token
       GetNextToken
      end
     else
     if (IsReservedWord(Token, rwDESC)) then
      begin
       OrderBySpecs[OrderBySpecsCount-1].Descending := True;
       // skip DESC token
       GetNextToken;
      end;
   end
  else
   raise EABSException.Create(30187, ErrorGFieldNameExpected,
                        [Token.Text, Token.LineNum, Token.ColumnNum]);
end;// ParseSortSpecification


//------------------------------------------------------------------------------
// ORDER BY
//------------------------------------------------------------------------------
function TABSSQLCursorCommand.ParseOrderByClause: Boolean;
begin
 if (IsReservedWord(Token, rwORDER)) then
  begin
    Result := True;
    // skip ORDER token
    GetNextToken;
    // 'BY' ?
    if (not IsReservedWord(Token, rwBY)) then
     raise EABSException.Create(30188, ErrorGOtherTokenExpected,
                  ['BY', Token.Text, Token.LineNum, Token.ColumnNum]);
    // skip BY token
    GetNextToken;

    // parse sort specification list
    if (ParseSortSpecification) then
     // get all specs
     repeat
        // end of sql command?
        if (Token.TokenType <> tktComma) then
          break
        else
          // skip ',' token
          GetNextToken([tktString, tktQuotedString, tktBracketedString, tktReservedWord, tktInt],
                       30189, ErrorGUnexpectedToken);
       // get next spec
       if (not ParseSortSpecification) then
        raise EABSException.Create(30190, ErrorGSortSpecificationExpected,
                        [Token.Text, Token.LineNum, Token.ColumnNum]);

     until False
    else
      raise EABSException.Create(30191, ErrorGSortSpecificationExpected,
                        [Token.Text, Token.LineNum, Token.ColumnNum]);
  end
 else
  Result := False;
end;// ParseOrderByClause


//------------------------------------------------------------------------------
// applies Order By clause
//------------------------------------------------------------------------------
procedure TABSSQLCursorCommand.ApplyOrderBy(AO: TABSAO);
var
  i: integer;
  ColumnName, TableName: AnsiString;
  FieldNumber: TABSIntegerArray;
  FieldName: string;
  IndexFieldNames, DescFields: string;
begin
  if (OrderBySpecsCount = 0) then
   exit;
  FieldNumber := TABSIntegerArray.Create;
  try
    // for all sort specs
    for i := 0 to OrderBySpecsCount-1 do
     begin
      ColumnName := OrderBySpecs[i].ColumnName;
      TableName := OrderBySpecs[i].TableName;

      // replace '1' -> field name
      if ((TableName = '') and (StrToIntDef(ColumnName, -1) <> -1)) then
          ColumnName := AO.GetFieldNameByVisibleNumber(StrToInt(ColumnName)-1);
      // get field No in AO
      FieldNumber.SetSize(0);
      AO.FieldExists(ColumnName, TableName,
                     not (AO is TABSAOGroupBy), FieldNumber);
      if (FieldNumber.ItemCount = 0) then
       raise EABSException.Create(30195, ErrorGColumnFromOrderByNotFound, [ColumnName]);
{
      if (FieldNumber.ItemCount > 1) then
       raise EABSException.Create(10412, ErrorLAmbiguousFieldReferenceInOrderBy,
          [TableName,ColumnName]);
}
      FieldName := AO.GetFieldName(FieldNumber.Items[0],True);
      // all index fields
      if (IndexFieldNames = '') then
       IndexFieldNames := FieldName
      else
       IndexFieldNames := IndexFieldNames + ';'+FieldName;
      // desc index fields
      if (OrderBySpecs[i].Descending) then
       if (DescFields = '') then
        DescFields := FieldName
       else
        DescFields := DescFields + ';'+FieldName;
     end;
     AO.SetIndex(IndexFieldNames, DescFields, '');
  finally
   FieldNumber.Free;
  end;
end;// ApplyOrderBy

//------------------------------------------------------------------------------
// Constructor
//------------------------------------------------------------------------------
constructor TABSSQLCursorCommand.Create(Lexer: TABSLexer; Query: TDataSet);
begin
  RootAO := nil;
  IntoTable := '';
  IntoDatabase := '';
  inherited Create(Lexer, Query);
end;//Create


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


//------------------------------------------------------------------------------
// executes query
//------------------------------------------------------------------------------
procedure TABSSQLCursorCommand.ExecSQL(
                                        query: TDataset;
                                        IsRoot,
                                        RequestLive: boolean;
                                        var ReadOnly: boolean;
                                        ParentQueryAO: TABSAO = nil;
                                        ParentCursor: TABSCursor = nil
                                      );
var
  dest: TABSTable;
  log: string;
  OldSilentMode: Boolean;
begin
  RowsAffected := 0;

  if (RootAO <> nil) then
    RootAO.Free;

  RootAO := BuildAOTree(query, RequestLive, ParentQueryAO, ParentCursor, False);

  if ((not RootAO.FIsAOTable) and (IntoTable <> '')) then
   begin
     if (not IntoMemory) then
      if (IntoDatabase = '') then
       IntoDatabase := TABSDataset(query).DatabaseName;
     RootAO.SetResultTable(IntoMemory or TABSDataset(query).InMemory, IntoImmediate,
                           IntoTable, IntoDatabase );
   end;

  if (OpenAndLockTablesBeforeSelect) then
    begin
      try
        RootAO.Execute(IsRoot, ParentQueryAO, ParentCursor);
      finally
        UnlockTablesAfterSelect;
      end;
    end
  else
    raise EABSException.Create(20225, ErrorATableLocked);

  // specify read-only result?
  if (IsRoot) then
   begin
    ReadOnly := False;
    if (not RequestLive) then
      ReadOnly := True
    else
     begin
      // not table => not updatable result
      if (not (RootAO is TABSAOTable)) and (IntoTable = '')  then
       ReadOnly := True;
     end;
    TABSDataSet(RootAO.ResultDataset).Handle.ReadOnly := ReadOnly;
   end;

  // select into table?
  if (IntoTable <> '') and (RootAO.FIsAOTable or (RootAO.FResultTableName = '')) then
   begin
    dest:= TABSTable.Create(nil);
    try
      dest.TableName := IntoTable;
      dest.InMemory := IntoMemory or TABSDataset(query).InMemory;
      if (dest.InMemory) then
        dest.DatabaseName := ABSMemoryDatabaseName
      else
        dest.DatabaseName := TABSDataset(query).DatabaseName;
      dest.SessionName := TABSDataset(query).SessionName;
      OldSilentMode := TABSDataset(query).Database.SilentMode;
      try
        TABSDataset(query).Database.SilentMode := True;
        TABSTable(RootAO.ResultDataset).AdvIndexDefs.Clear;
        if (not dest.ImportTable(RootAO.ResultDataset,log)) then
          raise EABSException.Create(10403,ErrorLImportTableCannotCopyData,[log]);
      finally
        TABSDataset(query).Database.SilentMode := OldSilentMode;
      end;
    finally
     dest.Free;
    end;
   end;

end;// ExecSQL


//------------------------------------------------------------------------------
// get result cursor
//------------------------------------------------------------------------------
function TABSSQLCursorCommand.GetResultCursor: TABSCursor;
begin
  Result := TABSDataSet(RootAO.ResultDataset).Handle;
end;//GetResultCursor


//------------------------------------------------------------------------------
// get result dataset
//------------------------------------------------------------------------------
function TABSSQLCursorCommand.GetResultDataset: TDataset;
begin
 Result := RootAO.ResultDataset;
end;// GetResultDataset



////////////////////////////////////////////////////////////////////////////////
//
// TABSTableReference
//
////////////////////////////////////////////////////////////////////////////////


//------------------------------------------------------------------------------
// Constructor
//------------------------------------------------------------------------------
constructor TABSTableReference.Create;
begin
  TableType := attTable;
  LeftTable := nil;
  RightTable := nil;
  SearchCondition := nil;
  UsingFields := nil;
  SubQuery := nil;
  ExternalDBToFree := nil;
end;//Create


//------------------------------------------------------------------------------
// Destructor
//------------------------------------------------------------------------------
destructor TABSTableReference.Destroy;
begin
  if (LeftTable <> nil) then
    LeftTable.Free;
  if (RightTable <> nil) then
    RightTable.Free;
  if (UsingFields <> nil) then
    UsingFields.Free;
  if (SearchCondition <> nil) then
    SearchCondition.Free;
  if (SubQuery <> nil) then
    SubQuery.Free;
  if (ExternalDBToFree <> nil) then
    ExternalDBToFree.Free;
end;//Destroy


//------------------------------------------------------------------------------
// makes join (left and right node-table)
//------------------------------------------------------------------------------
procedure TABSTableReference.MakeJoin(RightNode: TABSTableReference; JType: TABSJoinType;
                 IsNatural: Boolean; Fields: TStringList;
                 OnCondition: TABSExpression);
var
 LeftNode: TABSTableReference;
begin
 // copy current node to left
 LeftNode := TABSTableReference.Create;
 LeftNode.TableType := TableType;
 LeftNode.DatabaseName := DatabaseName;
 LeftNode.TableName := TableName;
 LeftNode.Pseudonym := Pseudonym;
 LeftNode.Password := Password;
 LeftNode.InMemory := InMemory;
 LeftNode.NaturalJoin := NaturalJoin;
 LeftNode.JoinType := JoinType;
 LeftNode.LeftTable := LeftTable;
 LeftNode.RightTable := RightTable;
 LeftNode.UsingFields := UsingFields;
 LeftNode.SearchCondition := SearchCondition;
 LeftNode.Subquery := Subquery;

 // set childs
 LeftTable := LeftNode;
 RightTable := RightNode;

 // set current data as join
 TableType := attJoinedTable;
 JoinType := JType;
 NaturalJoin := IsNatural;
 UsingFields := Fields;
 SearchCondition := OnCondition;
 Subquery := nil;
end; // MakeJoin


//------------------------------------------------------------------------------
// makes subquery
//------------------------------------------------------------------------------
procedure TABSTableReference.MakeSubQuery(Lexer: TABSLexer; Query: TDataSet);
begin
  TableType := attSubQuery;
  SubQuery := TABSSQLUnion.Create(Lexer, Query);
end;// MakeSubQuery



////////////////////////////////////////////////////////////////////////////////
//
// TABSSQLInsert
//
////////////////////////////////////////////////////////////////////////////////


//------------------------------------------------------------------------------
// Constructor
//------------------------------------------------------------------------------
constructor TABSSQLInsert.Create(Lexer: TABSLexer; Query: TDataSet);
begin
  FieldNames := TStringList.Create;
  SetLength(FieldValues,0);
  InternalSelecter := nil;
  DatabaseFileName := '';
  inherited Create(Lexer, Query);
end;//Create


//------------------------------------------------------------------------------
// Destructor
//------------------------------------------------------------------------------
destructor TABSSQLInsert.Destroy;
var
  i,j: integer;
begin
  Password:='';
  FieldNames.Free;
  for i:=0 to Length(FieldValues)-1 do
    begin
      for j:=0 to FieldValues[i].Count-1 do
        TABSExpression(FieldValues[i][j]).Free;
      FieldValues[i].Free;  
    end;
  if Assigned(InternalSelecter) then
    InternalSelecter.Free;
  inherited;
end;//Destroy


//------------------------------------------------------------------------------
// ParseValuesList
//------------------------------------------------------------------------------
procedure TABSSQLInsert.ParseValuesList;
var
  expr: TABSExpression;
begin
  // values list...
  repeat
    // '(' ?
    if Token.TokenType <> tktLeftParenthesis then
      Exit;

    SetLength(FieldValues, Length(FieldValues)+1);
    FieldValues[Length(FieldValues)-1] := TList.Create;
    repeat
      if (not GetNextToken) then
        raise EABSException.Create(30203, ErrorGUnexpectedEndOfCommand,
                                   [Token.LineNum, Token.ColumnNum]);

      expr := TABSExpression.Create;
      expr.ParseForValueExpression(LQuery, LLex);
      GetCurrentToken;
      if expr.IsEmpty then
        raise EABSException.Create(30357, ErrorGParseParameterError,
                                   [Token.Text, Token.LineNum, Token.ColumnNum]);

      FieldValues[Length(FieldValues)-1].Add(expr);

      //GetNextToken([tktComma, tktRightParenthesis], 02074);
    until Token.TokenType = tktRightParenthesis; // ')'
    GetNextToken;
    if (Token.TokenType <> tktComma) then
      break
    else
      GetNextToken;
  until (false);

end;//ParseValuesList


//------------------------------------------------------------------------------
// parses query
//------------------------------------------------------------------------------
procedure TABSSQLInsert.Parse;
begin
  if not GetCurrentToken then
    raise EABSException.Create(30204, ErrorGBlankSqlCommand);

  if (not GetNextToken) then
    raise EABSException.Create(30205, ErrorGUnexpectedEndOfCommand,
                               [Token.LineNum, Token.ColumnNum]);

  // INTO
  if not IsReservedWord(Token, rwINTO) then
    raise EABSException.Create(30206, ErrorGOtherTokenExpected,
                         ['INTO', Token.Text, Token.LineNum, Token.ColumnNum]);

  // MEMORY
  GetNextToken([tktReservedWord, tktParameter, tktString,tktQuotedString,tktBracketedString],
                   30207, ErrorGTableNameExpected);

  if IsReservedWord(Token, rwMEMORY) then
   begin
    IntoMemory := true;
    GetNextToken([tktReservedWord,tktParameter, tktString,tktQuotedString, tktBracketedString],
                 30208, ErrorGTableNameExpected);
   end;

  // table_name
  if (Token.TokenType <> tktParameter) then
    TableName := Token.Text
  else
    TableName := string(Token.ParamValue.AsAnsiString);
  // next token
  if (not GetNextToken) then
    raise EABSException.Create(30209, ErrorGUnexpectedEndOfCommand,
                               [Token.LineNum, Token.ColumnNum]);

  // db.table?
  if (Token.TokenType = tktDot) then
     begin
      DatabaseFileName := TableName;
      // get table name
      GetNextToken([tktString, tktParameter, tktBracketedString, tktQuotedString],
                   30493, ErrorGTableNameExpected);
      if (Token.TokenType <> tktParameter) then
        TableName := Token.Text
      else
        TableName := string(Token.ParamValue.AsAnsiString);
      GetNextToken;
     end;

  // PASSWORD ?

  if IsReservedWord(Token, rwPASSWORD) then
    begin
      GetNextToken([tktQuotedString, tktParameter], 30210, ErrorGQuotedPasswordDataExpected);
      if (Token.TokenType <> tktParameter) then
        Password := Token.Text
      else
        Password := string(Token.ParamValue.AsAnsiString);
      if (not GetNextToken) then
        raise EABSException.Create(30211, ErrorGUnexpectedEndOfCommand,
                                   [Token.LineNum, Token.ColumnNum]);
    end;
  // '(' ?
  if Token.TokenType = tktLeftParenthesis then
    begin
      LLex.LookNextToken(Token);
      if Token.ReservedWord = rwSELECT then
        begin
          GetNextToken;
          InternalSelecter := TABSSQLSelect.Create(LLex, LQuery);
          GetCurrentToken;
          if (Token.TokenType <> tktRightParenthesis) then
            raise EABSException.Create(20256, ErrorGMissingRightParenthesis,
                                      [Token.Text, Token.LineNum, Token.ColumnNum]);
          // skip ')'
          GetNextToken;
        end
      else
        begin
          // list...
          repeat
            GetNextToken;
            if not (Token.TokenType in [tktString,tktQuotedString,
                                        tktBracketedString,tktReservedWord]) then
              raise EABSException.Create(02156, ErrorGFieldNameExpected,
                                      [Token.Text, Token.LineNum, Token.ColumnNum]);

            //GetNextToken(02073);
            FieldNames.Add(Token.Text);

            GetNextToken([tktComma, tktRightParenthesis],
                         30212, ErrorGRightParenthesisOrCommaExpected);
          until Token.TokenType = tktRightParenthesis;
          // ')'
          if (not GetNextToken) then
            raise EABSException.Create(30213, ErrorGUnexpectedEndOfCommand,
                                       [Token.LineNum, Token.ColumnNum]);
        end;
    end;
  // 'VALUES' ?
  if Token.ReservedWord = rwVALUES then
    begin
      GetNextToken([tktLeftParenthesis], 30214, ErrorGLeftParenthesisExpected);
      ParseValuesList;
      if (FieldNames.Count > 0) and (Length(FieldValues) > 0) and (FieldNames.Count <> FieldValues[0].Count) then
        raise EABSException.Create(20259, ErrorAInvalidNumberOfFieldValues,
                                     [FieldNames.Count, FieldValues[0].Count, Token.LineNum, Token.ColumnNum]);
    end
  else
    begin
      if (Token.TokenType = tktLeftParenthesis) or (Token.ReservedWord = rwSELECT ) or (Token.TokenType = tktRightParenthesis)
        or (Token.TokenType = tktNone) then
      begin
        if Token.TokenType = tktLeftParenthesis then
          GetNextToken;
        if Token.ReservedWord = rwSELECT then
          InternalSelecter := TABSSQLSelect.Create(LLex, LQuery);
        GetCurrentToken;
        if Token.TokenType = tktRightParenthesis then
          GetNextToken;
      end
      else
        raise EABSException.Create(30213, ErrorGUnexpectedEndOfCommand,
          [Token.LineNum, Token.ColumnNum]);
    end;
end;//Parse


//------------------------------------------------------------------------------
// executes query
//------------------------------------------------------------------------------
procedure TABSSQLInsert.ExecSQL(query: TDataset; IsRoot, RequestLive: boolean;
                      var ReadOnly: boolean; ParentQueryAO: TABSAO = nil;
                      ParentCursor: TABSCursor = nil);
var
   DB:    TABSDatabase;
   T:     TABSTable;
   i,j:     Integer;
   ds:    TDataSet;
   TmpTable: TABSTable;
   s: string;
   FieldNo: Integer;
   Data:      TABSVariant;
   IsXIRWLockSet: Boolean;

procedure InsertSelect;
var
   i:     Integer;
   SrcField, DestField: TField;
begin
  while not TmpTable.Eof do
    begin
      try
        T.Insert;
        for i:=0 to TmpTable.Fields.Count-1 do
          begin
           //T.Fields.FieldByName(TmpTable.Fields[i].FieldName).Value := TmpTable.Fields[i].Value;
           // insert all fields
           SrcField := TmpTable.Fields[i];
           if (FieldNames.Count = 0) then
             DestField := T.Fields.Fields[i]
           else
             DestField := T.Fields.FieldByName(FieldNames[i]);
           AssignField(SrcField,DestField);
          end;
        T.Post;
      except
        T.Cancel;
        raise;
      end;
      Inc(RowsAffected);
      TmpTable.Next;
    end;
end;

begin
  RowsAffected := 0;

  inherited CreateDbAndTableComponents(query,DatabaseFileName,TableName,Password,IntoMemory,TDataset(T),TObject(DB));
  try
      T.Open;
      if InternalSelecter = nil then
        begin
          for j:=0 to Length(FieldValues)-1 do
            begin
              try
                T.Insert;
                for i:=0 to FieldValues[j].Count-1 do
                  begin
                    // Get Field No
                    FieldNo := i;
                    if (FieldNames.Count = FieldValues[j].Count) then
                     if (FieldNames.Count > 0) then
                      if (FieldNames[i] <> '') then
                       FieldNo := T.Fields.IndexOf(T.Fields.FieldByName(FieldNames[i]));
                    Data := TABSExpression(FieldValues[j][i]).GetValue;
                    if (Data <> nil) then
                      SetABSVariantIntoField(Data, T.Fields.Fields[FieldNo])
                    else
                      raise EABSException.Create(30356, ErrorGParameterValueUndefined,
                            [FieldNames[i], AftToStr(FieldTypeToABSAdvFieldType(
                                     T.Fields.FieldByName(FieldNames[i]).DataType))]);
                  end;
                  T.Post;
                except
                  T.Cancel;
                  raise;
                end;
              Inc(RowsAffected);
            end;
        end
      else
        begin
          InternalSelecter.ExecSQL(query, IsRoot, false, ReadOnly);

          ds := InternalSelecter.GetResultDataset;

          TmpTable := TABSTable.Create(nil);
          try
            TmpTable.Temporary := True;
            if (TABSQuery(query).InMemory) then
              TmpTable.DisableTempFiles := True
            else
              TmpTable.DisableTempFiles := TABSQuery(query).Database.DisableTempFiles;
            TmpTable.TableName := GetTemporaryName('TmpTableForInsert');
            TmpTable.ImportTable(ds,s);

            TmpTable.Open;
            TmpTable.First;

            if (T.Handle.Session.InTransaction) then
              InsertSelect
            else
              if (T.Handle.LockTable(ltRW,
                  BatchUpdateTableLockRetries, BatchUpdateTableLockDelay)) then
                begin
                  try
                    IsXIRWLockSet := T.Handle.LockTable(ltXIRW, 1, 0, False);
                    T.Handle.BeginBatchUpdate;
                    try
                      InsertSelect;
                    except
                      T.Handle.CancelBatchUpdate;
                      raise;
                    end;
                  finally
                    T.Handle.EndBatchUpdate;
                    if (IsXIRWLockSet) then
                      T.Handle.UnlockTable(ltXIRW);
                    T.Handle.UnlockTable(ltRW);
                  end;
                end
              else
                raise EABSException.Create(20224, ErrorATableLocked);
            TmpTable.Close;
            TmpTable.DeleteTable;
          finally
            TmpTable.Free;
          end;
        end;
      T.Close;
  finally
    FreeDbAndTableComponents(T,DB);
  end;
end;//ExecSQL



////////////////////////////////////////////////////////////////////////////////
//
// TABSSQLUpdate
//
////////////////////////////////////////////////////////////////////////////////


//------------------------------------------------------------------------------
// Constructor
//------------------------------------------------------------------------------
constructor TABSSQLUpdate.Create(Lexer: TABSLexer; Query: TDataSet);
begin
  InternalSelecter := nil;
  FieldNames  := TStringList.Create;
  FieldValues := TList.Create;
  TableName := '';
  FSelectLexer := TABSLexer.Create('');
  FieldValuesSubqueryExpr := nil;

  inherited Create(Lexer, Query);
end;//Create


//------------------------------------------------------------------------------
// Destructor
//------------------------------------------------------------------------------
destructor TABSSQLUpdate.Destroy;
var i: integer;
begin
  FSelectLexer.Free;
  if Assigned(InternalSelecter) then
    InternalSelecter.Free;
  FieldNames.Free;
  for i:=0 to FieldValues.Count-1 do
    TABSExpression(FieldValues[i]).Free;
  FieldValues.Free;
  if (FieldValuesSubqueryExpr <> nil) then
    FieldValuesSubqueryExpr.Free;
  inherited;
end;


//------------------------------------------------------------------------------
// parses query
//------------------------------------------------------------------------------
procedure TABSSQLUpdate.ExecSQL(query: TDataset; IsRoot, RequestLive: boolean;
                            var ReadOnly: boolean; ParentQueryAO: TABSAO = nil;
                            ParentCursor: TABSCursor = nil);
var
  ds:    TABSDataSet;
  tbl:   TABSTable;

procedure RunTableLoop;
var
  i:     Integer;
  Bookmark: TBookmark;
begin
  ds.First;
  while not ds.Eof do
    begin

      Bookmark := ds.GetBookmark;
      try
        tbl.GotoBookmark(Bookmark);
      finally
        ds.FreeBookmark(Bookmark);
      end;
      ds.Next;

      if (not IsFieldValuesSubquery) then
        begin
          for i:=0 to FieldValues.Count-1 do
            TABSExpression(FieldValues[i]).AssignCursor(tbl.Handle, nil, nil);

          tbl.Edit;
          try
            for i:=0 to FieldNames.Count-1 do
              SetABSVariantIntoField(
                                    TABSExpression(FieldValues[i]).GetValue,
                                    tbl.Fields.FieldByName(FieldNames[i])
                                    );
            tbl.Post;
          except         // 5.06
            tbl.Cancel;  // 5.06
            raise;       // 5.06
          end;
        end
      else
        begin
          FieldValuesSubqueryExpr.AssignCursor(tbl.Handle, nil, nil);
          FieldValuesSubqueryExpr.ResetSubqueryFieldValues;

          tbl.Edit;
          for i:=0 to FieldNames.Count-1 do
            SetABSVariantIntoField(
                                    FieldValuesSubqueryExpr.GetSubqueryFieldValue(i),
                                    tbl.Fields.FieldByName(FieldNames[i])
                                  );
          tbl.post;
        end;
      Inc(RowsAffected);
    end;
end;

procedure RunUpdate;
var
  i:     Integer;
  bUpdateSucceeded: Boolean;
  IsIXRWLockSet: Boolean;

function SetTableLocks: Boolean;
var
  j: Integer;
begin
  Result := False;
  IsIXRWLockSet := False;
  for j:=1 to BatchUpdateTableLockRetries do
    begin
      if (ds.Handle.LockTable(ltRW, 1, 0)) then
        begin
          Result := True;
          if (ds.Handle.LockTable(ltXIRW, 1, 0, False)) then
            IsIXRWLockSet := True;
          break;
        end;
      ds.Handle.UnlockTableData;
      Sleep(BatchUpdateTableLockDelay);
      ds.Handle.LockTableData;
    end;
end;

begin
  for i:=0 to FieldValues.Count-1 do
    TABSExpression(FieldValues[i]).AssignCursor(tbl.Handle, nil, nil);

  if (not ds.Handle.Session.InTransaction) then
    begin
      i := 1;
      bUpdateSucceeded := false;
      while (i <= BatchUpdateTableIfFailedRetries) do
        begin
          if (SetTableLocks) then
            begin
              try
                tbl.Handle.BeginBatchUpdate;
                try
                  RunTableLoop;
                  tbl.Handle.EndBatchUpdate;
                  bUpdateSucceeded := True;
                  break;
                except
                  tbl.Handle.CancelBatchUpdate;
                  if (i = BatchUpdateTableIfFailedRetries) then
                    raise
                end;
                Inc(i);
              finally
                if (IsIXRWLockSet) then
                  ds.Handle.UnlockTable(ltXIRW);
                ds.Handle.UnlockTable(ltRW);
                if (not bUpdateSucceeded) then
                  Sleep(BatchUpdateTableIfFailedDelay);
              end;
            end
          else
            raise EABSException.Create(20223, ErrorATableLocked);
        end;
    end
  else
    RunTableLoop;
end;

begin
  RowsAffected := 0;

  InternalSelecter.ExecSQL(query, IsRoot, True, ReadOnly);
  ds := TABSDataset(InternalSelecter.GetResultDataset);
  tbl := TABSTable.Create(nil);
  try
    tbl.DatabaseName := ds.DatabaseName;
    tbl.SessionName := ds.SessionName;
    tbl.TableName := TABSTable(ds).TableName;
    tbl.InMemory := ds.InMemory;
    tbl.Open;

    ds.Handle.LockTableData;
    try
      RunUpdate
    finally
      ds.Handle.UnlockTableData;
    end;
  finally
    tbl.Free;
  end;
end;//ExecSQL


//------------------------------------------------------------------------------
// parses query
//------------------------------------------------------------------------------
procedure TABSSQLUpdate.Parse;
var
  oldPos, i: Integer;
  Expr: TABSExpression;
  TmpToken: TToken;
begin
  if not GetCurrentToken then
    raise EABSException.Create(30216, ErrorGBlankSqlCommand);

  GetNextToken([tktReservedWord,tktParameter,tktString,tktQuotedString,tktBracketedString],
               30217, ErrorGTableNameExpected);

  ParseTableNameToken(DatabaseFileName, TableName, FTablePseudonym, Password, IntoMemory);

  // SET
  if not IsReservedWord(Token, rwSET) then
    raise EABSException.Create(30220, ErrorGOtherTokenExpected,
             ['SET', Token.Text, Token.LineNum, Token.ColumnNum]);

  GetNextToken;

  // "update f1=expr1, f2=expr2" or "update set (f1,f2)=(select a1,a2)" ?
  if (Token.TokenType <> tktLeftParenthesis) then
    begin
      IsFieldValuesSubquery := False;
      // field_name = value, ...
      repeat
        // Field_name
        FieldNames.Add(Token.Text);
        GetNextToken;

        // check for table_name.field_name = value
        if ( (UpperCase(TableName) = UpperCase(FieldNames[FieldNames.count-1])) and
             (Token.TokenType = tktDot) ) then
          begin
           GetNextToken;
           FieldNames[FieldNames.count-1] := Token.Text;
           GetNextToken;
          end;

        if Token.Text <> '=' then
          raise EABSException.Create(30221, ErrorGOtherTokenExpected,
                       ['=', Token.Text, Token.LineNum, Token.ColumnNum]);
        GetNextToken;

        Expr := TABSExpression.Create;
        expr.ParseForValueExpression(LQuery, LLex);
        FieldValues.Add(Expr);

        if not GetCurrentToken then
          // end of command
          break
        else
          begin
            // ',' or 'WHERE'
            if Token.ReservedWord = rwWHERE then
              break;

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

            // get next field_name
            if (not GetNextToken) then
                raise EABSException.Create(30223, ErrorGUnexpectedEndOfCommand,
                                           [Token.LineNum, Token.ColumnNum]);
          end;
      until false;
    end
  else
    // update set (f1,f2)=(select a1,a2)
    begin
      IsFieldValuesSubquery := True;
      GetNextToken;
      // get (field names)
      repeat
        // Field_name
        FieldNames.Add(Token.Text);
        GetNextToken;

        // check for table_name.field_name = value
        if ( (UpperCase(TableName) = UpperCase(FieldNames[FieldNames.count-1])) and
             (Token.TokenType = tktDot) ) then
          begin
           GetNextToken;
           FieldNames[FieldNames.count-1] := Token.Text;
           GetNextToken;
          end;

        if Token.TokenType <> tktComma then
          break
        else
          GetNextToken;
      until (false);
      if (Token.TokenType = tktRightParenthesis) then
        GetNextToken
      else
        raise EABSException.Create(30494, ErrorGOtherTokenExpected,
                       [')', Token.Text, Token.LineNum, Token.ColumnNum]);

      if (Token.Text <> '=') then
        raise EABSException.Create(30495, ErrorGOtherTokenExpected,
                       ['=', Token.Text, Token.LineNum, Token.ColumnNum]);
      GetNextToken;

      // get subquery of values
      FieldValuesSubqueryExpr := TABSExpression.Create;
      FieldValuesSubqueryExpr.ParseForRowSubqueryExpression(LQuery, LLex);
      GetCurrentToken;
    end;

 // sql: select f1, f2 from table_name where .......
 //      update table_name set a=b, c=d where .......
  with  LLex.Commands[LLex.CurrentCommandNo] do
    begin
      // Free Params
      for i:=0 to CurrentTokenNo-1 do
        if Tokens[i].ParamValue <> nil then
          begin
            Tokens[i].ParamValue.Free;
            Tokens[i].ParamValue := nil;
          end;

      oldPos := CurrentTokenNo;

      FSelectLexer.AddCommand;
      TmpToken.TokenType := tktReservedWord;
      TmpToken.ReservedWord := rwSELECT;
      TmpToken.Text := 'select';
      TmpToken.ParamValue := nil;
      FSelectLexer.AddToken(TmpToken);

      TmpToken.TokenType := tktNone;
      TmpToken.ReservedWord := rwNone;
      TmpToken.Text:=Asterisk;
      TmpToken.ParamValue := nil;
      FSelectLexer.AddToken(TmpToken);

      TmpToken.TokenType := tktReservedWord;
      TmpToken.ReservedWord := rwFROM;
      TmpToken.Text:='from';
      TmpToken.ParamValue := nil;
      FSelectLexer.AddToken(TmpToken);

      if IntoMemory then
       begin
        TmpToken.TokenType := tktReservedWord;
        TmpToken.ReservedWord := rwMEMORY;
        TmpToken.Text:=ABSMemoryDatabaseName;
        TmpToken.ParamValue := nil;
        FSelectLexer.AddToken(TmpToken);
       end;

      if (DatabaseFileName <> '') then
        begin
          TmpToken.TokenType := tktQuotedString;
          TmpToken.ReservedWord := rwNone;
          TmpToken.Text:=DatabaseFileName;
          TmpToken.ParamValue := nil;
          FSelectLexer.AddToken(TmpToken);

          TmpToken.TokenType := tktDot;
          TmpToken.ReservedWord := rwNone;
          TmpToken.Text:='.';
          TmpToken.ParamValue := nil;
          FSelectLexer.AddToken(TmpToken);
        end;

      TmpToken.TokenType := tktString;
      TmpToken.ReservedWord := rwNone;
      TmpToken.Text:=TableName;
      TmpToken.ParamValue := nil;
      FSelectLexer.AddToken(TmpToken);

      if (FTablePseudonym <> '') then
        begin
          TmpToken.TokenType := tktString;
          TmpToken.ReservedWord := rwNone;
          TmpToken.Text:=FTablePseudonym;
          TmpToken.ParamValue := nil;
          FSelectLexer.AddToken(TmpToken);
        end;

      if (Password <> '') then
        begin
          TmpToken.TokenType := tktReservedWord;
          TmpToken.ReservedWord := rwPassword;
          TmpToken.Text:=Password;
          TmpToken.ParamValue := nil;
          FSelectLexer.AddToken(TmpToken);

          TmpToken.TokenType := tktQuotedString;
          TmpToken.ReservedWord := rwNone;
          TmpToken.Text:=Password;
          TmpToken.ParamValue := nil;
          FSelectLexer.AddToken(TmpToken);
        end;

      //  add Tokens (where clause) ?
      if IsReservedWord(Token, rwWHERE) then
        for i:=oldPos to length(Tokens)-1 do
          begin
            FSelectLexer.AddToken(Tokens[i]);
            Tokens[i].ParamValue := nil;
          end;
      FSelectLexer.SetCurrentTokenNo(0);

      InternalSelecter := TABSSQLSelect.Create(FSelectLexer, LQuery);
    end;
end;//Parse



////////////////////////////////////////////////////////////////////////////////
//
// TABSSQLDelete
//
////////////////////////////////////////////////////////////////////////////////


//------------------------------------------------------------------------------
// Constructor
//------------------------------------------------------------------------------
constructor TABSSQLDelete.Create(Lexer: TABSLexer; Query: TDataSet);
begin
  InternalSelecter := nil;
  FullDelete := false;
  TableName := '';
  inherited Create(Lexer, Query);
end;//Create


//------------------------------------------------------------------------------
// Destructor
//------------------------------------------------------------------------------
destructor TABSSQLDelete.Destroy;
begin
  if Assigned(InternalSelecter) then
    InternalSelecter.Free;
  inherited;
end;//Destroy


//------------------------------------------------------------------------------
// Destructor
//------------------------------------------------------------------------------
procedure TABSSQLDelete.ExecSQL(query: TDataset; IsRoot, RequestLive: boolean;
                            var ReadOnly: boolean; ParentQueryAO: TABSAO = nil;
                            ParentCursor: TABSCursor = nil);
var
  ds: TABSDataSet;
  tbl:   TABSTable;
  j: Integer;
  TableLockSucceeded: Boolean;
  IsXIRWLockSet: Boolean;

procedure RunDeleteWithBitmap;
begin
  ds.Handle.BeginBatchUpdate;
  try
    try
      while not ds.Eof do
        begin
          ds.Delete;
          Inc(RowsAffected);
        end;
    except
      ds.Handle.CancelBatchUpdate;
      raise;
    end;
  finally
    ds.Handle.EndBatchUpdate;
  end;
end;


procedure RunTableLoop;
var
  Bookmark: TBookmark;
begin
  while not ds.Eof do
    begin
      Bookmark := ds.GetBookmark;
      try
        tbl.GotoBookmark(Bookmark);
      finally
        ds.FreeBookmark(Bookmark);
      end;
      ds.Next;
      tbl.Delete;
      Inc(RowsAffected);
    end;
end;

procedure RunDeleteWithoutBitmap;
begin
    if (not ds.Handle.Session.InTransaction) then
      begin
        tbl.Handle.BeginBatchUpdate;
        try
          RunTableLoop;
          tbl.Handle.EndBatchUpdate;
        except
          tbl.Handle.CancelBatchUpdate;
          raise;
        end;
      end
    else
      RunTableLoop;
end;

procedure RunDelete;
begin
    ds.First;
    if ((ds.Handle.RecordBitmap <> nil) and
        TABSRecordBitmap(ds.Handle.RecordBitmap).Active) then
      RunDeleteWithBitmap
    else
      RunDeleteWithoutBitmap;
end;

begin
  RowsAffected := 0;
  InternalSelecter.ExecSQL(query, IsRoot, True, ReadOnly);
  ds := TABSDataset(InternalSelecter.GetResultDataset);
  if ((ds.Handle.RecordBitmap <> nil) and
          TABSRecordBitmap(ds.Handle.RecordBitmap).Active) then
    tbl := nil
  else
    tbl := TABSTable.Create(nil);
  try
    if (tbl <> nil) then
      begin
        tbl.DatabaseName := ds.DatabaseName;
        tbl.SessionName := ds.SessionName;
        tbl.TableName := TABSTable(ds).TableName;
        tbl.InMemory := ds.InMemory;
        tbl.Open;
      end;

    ds.Handle.LockTableData;
    try
      if (ds.Handle.Session.InTransaction) then
        RunDelete
      else
        begin
          for j:=1 to BatchUpdateTableLockRetries do
            begin
              TableLockSucceeded := ds.Handle.LockTable(ltRW, 1, 0);
              if (TableLockSucceeded) then break;
              ds.Handle.UnlockTableData;
              Sleep(BatchUpdateTableLockDelay);
              ds.Handle.LockTableData;
            end;
          if (TableLockSucceeded) then
            begin
              // try to speed up deleting by avoiding table and record locks
              IsXIRWLockSet := ds.Handle.LockTable(ltXIRW, 1, 0, False);
              try
                RunDelete;
              finally
                if (IsXIRWLockSet) then
                  ds.Handle.UnlockTable(ltXIRW);
                ds.Handle.UnlockTable(ltRW);
              end;
            end
          else
            raise EABSException.Create(20223, ErrorATableLocked);
        end;
    finally
      ds.Handle.UnlockTableData;
    end;
  finally
    if (tbl <> nil) then
      tbl.Free;
  end;
end;//ExecSQL


//------------------------------------------------------------------------------
// parses query
//------------------------------------------------------------------------------
procedure TABSSQLDelete.Parse;
var
   i: integer;
begin
  // CONVERT DELETE TO SELECT
  with  LLex.Commands[LLex.CurrentCommandNo] do
    begin
      SetLength(Tokens, length(Tokens)+1);
      inc(NumTokens);
      for i:=length(Tokens)-1 downto 1 do
        begin
          Tokens[i] := Tokens[i-1];
        end;
      Tokens[0].TokenType := tktReservedWord;
      Tokens[0].ReservedWord := rwSELECT;
      Tokens[0].Text := 'select';
      Tokens[1].TokenType := tktNone;
      Tokens[1].ReservedWord := rwNone;
      Tokens[1].Text := Asterisk;
    end;
  if not GetCurrentToken then
    raise EABSException.Create(30224, ErrorGUnexpectedEndOfCommand,
                               [Token.LineNum, Token.ColumnNum]);

  InternalSelecter := TABSSQLSelect.Create(LLex, LQuery);

end;//Parse



////////////////////////////////////////////////////////////////////////////////
//
// TSQLFieldDef
//
////////////////////////////////////////////////////////////////////////////////


//------------------------------------------------------------------------------
// Constructor
//------------------------------------------------------------------------------
constructor TSQLFieldDef.Create;
begin
  FieldName := '';
  CreateIfNotExists := False;
  
  newFieldType := False;
  FieldType := aftUnknown;

  newLength := False;
  Length := 0;

  newRequired := False;

  DefaultValue := TABSVariant.Create;
  MinValue := TABSVariant.Create;
  MaxValue := TABSVariant.Create;

  // Blob settings
  newBlobblocksize := False;
  newBlobCompressionAlgorithm := False;
  newBlobCompressionMode := False;

  //Autoinc
  newAutoincIncrement := False;
  newAutoincLastValue := False;
  newAutoincMinValue := False;
  newAutoincMaxValue := False;
  newAutoincCycled := False;

  // Default Value
  newDefaultValue := False;
  // Min
  newMinValue := False;
  // Max
  newMaxValue := False;

  // PK
  newPrimaryKey := False;
  // Unique
  newUnique := False;

end;//Create


//------------------------------------------------------------------------------
// Destructor
//------------------------------------------------------------------------------
destructor TSQLFieldDef.Destroy;
begin
  DefaultValue.Free;
  MinValue.Free;
  MaxValue.Free;
end;//Destroy



////////////////////////////////////////////////////////////////////////////////
//
// TSQLFieldDefs
//
////////////////////////////////////////////////////////////////////////////////


//------------------------------------------------------------------------------
// Constructor
//------------------------------------------------------------------------------
constructor TSQLFieldDefs.Create;
begin
  List := TList.Create;
end;//Create


//------------------------------------------------------------------------------
// Destructor
//------------------------------------------------------------------------------
destructor TSQLFieldDefs.Destroy;
var i: Integer;
begin
  for i:=0 to List.Count-1 do
    Items[i].Free;
  List.Free;
end;//Destroy


//------------------------------------------------------------------------------
// Create SQLFieldDef and add it to List
//------------------------------------------------------------------------------
function TSQLFieldDefs.AddCreated: TSQLFieldDef;
begin
  Result := TSQLFieldDef.Create;
  List.Add(Result);
end;//AddCreated


//------------------------------------------------------------------------------
// Get Count
//------------------------------------------------------------------------------
function TSQLFieldDefs.GetCount: Integer;
begin
  Result := List.Count;
end;//GetCount


//------------------------------------------------------------------------------
// Get Def
//------------------------------------------------------------------------------
function TSQLFieldDefs.GetDef(Index: Integer): TSQLFieldDef;
begin
  Result := TSQLFieldDef(List[Index]);
end;//GetDef




////////////////////////////////////////////////////////////////////////////////
//
// TABSDDLTableManipulation
//
////////////////////////////////////////////////////////////////////////////////


//------------------------------------------------------------------------------
// Constructor
//------------------------------------------------------------------------------
constructor TABSDDLTableManipulation.Create(Lexer: TABSLexer; Query: TDataSet);
begin
  Password := '';
  DatabaseName:='';             
  TableName:='';             // Table Name
  SQLFieldDefs := TSQLFieldDefs.Create;
  //SetLength(PrimaryKeyFields, 0);
  //UniqueFields := TStringList.Create;
  //PrimaryIndexName := '';
  inherited Create(Lexer, Query);
end;//Create


//------------------------------------------------------------------------------
// Destructor
//------------------------------------------------------------------------------
destructor TABSDDLTableManipulation.Destroy;
begin
  SQLFieldDefs.Free;
  //UniqueFields.Free;
  inherited;
end;//Destroy


//------------------------------------------------------------------------------
// table element list
//------------------------------------------------------------------------------
procedure TABSDDLTableManipulation.ParseTableElementList;
var
  FieldDef: TSQLFieldDef;
begin
  if (Token.TokenType <> tktLeftParenthesis) then
    raise EABSException.Create(30226, ErrorGOtherTokenExpected,
            ['(', Token.Text, Token.LineNum, Token.ColumnNum]);

  // Columns Loop
  repeat
    // end of create table sql ')' ?
    if not LLex.GetCurrentToken(Token) then
      Break;
    if (Token.TokenType = tktRightParenthesis) then
      break;
    GetNextToken([tktString, tktQuotedString, tktBracketedString, tktReservedWord],
                 30227, ErrorGFieldNameExpected);

    // end of column list?
    if ParsePrimaryKey then continue;
    if ParseIndex then continue;

    // Field Params
    FieldDef := SQLFieldDefs.AddCreated;

    if IsReservedWord(Token, rwIF) then
     begin
      GetNextToken;
      if IsReservedWord(Token, rwNOT) then
        GetNextToken
      else
        raise EABSException.Create(30488, ErrorGOtherTokenExpected,
                            ['NOT',Token.Text, Token.LineNum, Token.ColumnNum]);
      if IsReservedWord(Token, rwEXISTS) then
        GetNextToken
      else
        raise EABSException.Create(30489, ErrorGOtherTokenExpected,
                            ['EXISTS',Token.Text, Token.LineNum, Token.ColumnNum]);
      FieldDef.CreateIfNotExists := True;
     end;

    // column name
    FieldDef.FieldName := Token.Text;

    GetNextToken([tktString, tktBracketedString, tktReservedWord],
                 30228, ErrorGFieldTypeExpected);


    // Fill Column Type
    FillColumnType(FieldDef);

    // until ',' or ')' or 'any_word' after FIELDNAME FIELDTYPE ...
    while not (Token.TokenType in [tktComma,tktRightParenthesis]) do
     begin

      // NULL or NOT NULL
      if Token.ReservedWord in [rwNOT,rwNULL] then
        // Fill Column Required value
        ParseColumnRequared(FieldDef)
      else

      // BlobBlockSize
      if IsReservedWord(Token, rwBLOBBLOCKSIZE) then
       begin
        FieldDef.newBlobBlockSize := true;
        FieldDef.BlobBlockSize := ParseInteger;
       end
      else

      // BlobCompressionMode
      if IsReservedWord(Token, rwBLOBCOMPRESSIONMODE) then
       begin
        FieldDef.newBlobCompressionMode := true;
        FieldDef.BlobCompressionMode := ParseInteger;
       end
      else

      // BlobCompressionAlgorithm
      if IsReservedWord(Token, rwBLOBCOMPRESSIONALGORITHM) then
       begin
        // {NONE | ZLIB | BZIP | PPM}
        GetNextToken;
        FieldDef.BlobCompressionAlgorithm := GetCompressionAlgorithm(Token.Text);
        FieldDef.newBlobCompressionAlgorithm := true;
       end
      // DEFAULT
      else if ParseDefaultValue(FieldDef) then Continue
      // MINVALUE
      else if ParseMinValue(FieldDef) then Continue
      // MAXVALUE
      else if ParseMaxValue(FieldDef) then Continue
      
      // PRIMARY KEY
      else if ParseFieldPrimaryKey(FieldDef) then Continue
      // UNIQUE
      else if ParseFieldUnique(FieldDef) then Continue;

      GetNextToken([tktComma, tktRightParenthesis,
                    tktString, tktQuotedString, tktBracketedString,
                    tktReservedWord],
                    30229, ErrorGRightParenthesisOrCommaExpected);


     end;//while
  until False;//Columns Loop
end;//ParseTableElementList


//------------------------------------------------------------------------------
// parse TableName token
//------------------------------------------------------------------------------
procedure TABSDDLTableManipulation.ParseTableNameToken;
begin
  inherited ParseTableNameToken(DatabaseName, TableName, TableAlias, Password, IntoMemory);
end;//ParseTableNameToken


//------------------------------------------------------------------------------
// Fill table column type into Structure
//------------------------------------------------------------------------------
procedure TABSDDLTableManipulation.FillColumnType(var FieldDef: TSQLFieldDef);
begin
  // column type
  FieldDef.FieldType := GetFieldType(Token.Text);
  FieldDef.newFieldType := True;

  // Unknown type ?
  if FieldDef.FieldType = aftUnknown then
    raise EABSException.Create(30232, ErrorGUnknownFieldType,
                 [Token.Text, Token.LineNum, Token.ColumnNum]);

  // sizeble type ? ex: String(255)
  if (FieldDef.FieldType <> aftGuid) and
     (IsStringFieldType(FieldDef.FieldType) or IsBytesFieldType(FieldDef.FieldType)) then
    begin
     // '('
     GetNextToken([tktLeftParenthesis],
                  30233, ErrorGLeftParenthesisExpected);
     // Int
     GetNextToken([tktInt], 30235, ErrorGDecimalConstantExpected);

     FieldDef.Length := StrToInt(Token.Text);
     FieldDef.newLength := True;
     
     // ')'
     GetNextToken([tktRightParenthesis],
                  30236, ErrorGRightParenthesisOrCommaExpected);
    end
  else
    FieldDef.Length := 0;

  if IsAutoincFieldType(FieldDef.FieldType) then
   if (GetNextToken) then
    // '('
    if Token.TokenType = tktLeftParenthesis then
     begin
      repeat
       if (not GetNextToken) then
        raise EABSException.Create(30290, ErrorGUnexpectedEndOfCommand,
                                  [Token.LineNum, Token.ColumnNum]);
       // ')'
       if (Token.TokenType = tktRightParenthesis) then
         break;

       // ','
       if (Token.TokenType = tktComma) then
         GetNextToken;


       case Token.ReservedWord of
         rwINCREMENT:
           begin
             FieldDef.AutoincIncrement := ParseInteger;
             FieldDef.newAutoincIncrement := True;
           end;
         rwINITIALVALUE,
         rwSTARTVALUE:
           begin
             FieldDef.AutoincLastValue := ParseInteger;
             FieldDef.newAutoincLastValue := True;
           end;
         rwMINVALUE:
           begin
             FieldDef.AutoincMinValue := ParseInteger;
             FieldDef.newAutoincMinValue := True;
           end;
         rwMAXVALUE:
           begin
             FieldDef.AutoincMaxValue := ParseInteger;
             FieldDef.newAutoincMaxValue := True;
           end;
         rwCYCLED:
           begin
             FieldDef.AutoincCycled := True;
             FieldDef.newAutoincCycled := True;
           end;
         rwNOMINVALUE:
           begin
             FieldDef.AutoincMinValue := 0;//Low(Int64);
             FieldDef.newAutoincMinValue := True;
           end;
         rwNOMAXVALUE:
           begin
             FieldDef.AutoincMaxValue := High(Int64);
             FieldDef.newAutoincMaxValue := True;
           end;
         rwNOCYCLED:
           begin
             FieldDef.AutoincCycled := False;
             FieldDef.newAutoincCycled := True;
           end;
         else
           begin
             if (FieldDef.FieldType = aftAutoinc) then
              begin
               FieldDef.FieldType := GetFieldType(Token.Text);
               // Convert to Autoinc type
               case FieldDef.FieldType of
                aftShortint:  FieldDef.FieldType := aftAutoIncShortint;
                aftSmallint:  FieldDef.FieldType := aftAutoIncSmallint;
                aftInteger:   FieldDef.FieldType := aftAutoInc;
                aftLargeint:  FieldDef.FieldType := aftAutoIncLargeint;
                aftByte:      FieldDef.FieldType := aftAutoIncByte;
                aftWord:      FieldDef.FieldType := aftAutoIncWord;
                aftCardinal:  FieldDef.FieldType := aftAutoIncCardinal;

                aftAutoInc,   // check for correct types
                aftAutoIncShortint,
                aftAutoIncSmallint,
                aftAutoIncInteger,
                aftAutoIncLargeint,
                aftAutoIncByte,
                aftAutoIncWord,
                aftAutoIncCardinal: ;
                else
                  raise EABSException.Create(30295, ErrorGUnsupportedAutoincDataType,
                                  [Token.Text, Token.LineNum, Token.ColumnNum]);
               end;//case

               if FieldDef.FieldType = aftUnknown then
                begin
                 FieldDef.FieldType := aftAutoinc;
                 raise EABSException.Create(30293, ErrorGUnexpectedToken,
                                  [Token.Text, Token.LineNum, Token.ColumnNum]);
                end
              end
             else
              raise EABSException.Create(30294, ErrorGUnexpectedToken,
                               [Token.Text, Token.LineNum, Token.ColumnNum]);

           end;//if
       end;// case
       
      until False;
      GetNextToken;
     end
    else
   else
  else
   GetNextToken;
end;//FillColumnType


//------------------------------------------------------------------------------
// Fill column requared value into Structure
//------------------------------------------------------------------------------
procedure TABSDDLTableManipulation.ParseColumnRequared(var FieldDef: TSQLFieldDef);
begin
  if IsReservedWord(Token, rwNOT) then
    begin
      GetNextToken([tktReservedWord], 30237, ErrorGUnexpectedEndOfCommand);
      // NOT NULL
      if IsReservedWord(Token, rwNULL) then
        begin
          FieldDef.newRequired := True;
          FieldDef.Required := True;
        end
      else
        raise EABSException.Create(02011, ErrorGNullKeywordExpected,
                                 [Token.Text, Token.LineNum, Token.ColumnNum]);
    end
  else
    // NULL
    if IsReservedWord(Token, rwNULL) then
      begin
        FieldDef.newRequired := True;
        FieldDef.Required := False;
      end;
end;//ParseColumnRequared


//------------------------------------------------------------------------------
// Fill BlobBlockSize value into Structure
//------------------------------------------------------------------------------
//procedure TABSDDLTableManipulation.FillBlobBlockSize(var FieldDef: TSQLFieldDef);
//begin
//  GetNextToken([tktInt], 30296, ErrorGIntegerExpected);
//  FieldDef.newBlobBlockSize := True;
//  FieldDef.BlobBlockSize := ParseInteger;
//end;//FillBlobBlockSize


//------------------------------------------------------------------------------
// parse PASSWORD 'aaa'
//------------------------------------------------------------------------------
procedure TABSDDLTableManipulation.ParsePassword;
begin
  if (Token.ReservedWord = rwPASSWORD) then
    begin
      GetNextToken([tktQuotedString, tktParameter], 30238, ErrorGQuotedPasswordDataExpected);
      if (Token.TokenType <> tktParameter) then
        Password := Token.Text
      else
        Password := string(Token.ParamValue.AsAnsiString);
      GetNextToken([tktComma, tktRightParenthesis],
                    30239, ErrorGRightParenthesisOrCommaExpected);
    end;
end;//ParsePassword


//------------------------------------------------------------------------------
// parse DEFAULT {const | NULL}
//------------------------------------------------------------------------------
function TABSDDLTableManipulation.ParseDefaultValue(var FieldDef:TSQLFieldDef): Boolean;
var
  expr: TABSExpression;
begin
  Result := False;
  if (Token.ReservedWord = rwDEFAULT) then
    begin
      GetNextToken([], 30297, ErrorGUnexpectedEndOfCommand);
      // 5.10 fix DEFAULT NOW problem
      if (UpperCase(Token.Text) = 'NOW') or
         (UpperCase(Token.Text) = '''NOW''') then
      begin
        FieldDef.DefaultValue.AsAnsiString  := 'NOW';
        FieldDef.newDefaultValue := True;
        GetCurrentToken;
        Exit;
      end;
      // 5.14 fix DEFAULT CURRENT_TIMESTAMP problem
      if (UpperCase(Token.Text) = 'CURRENT_TIMESTAMP') or
         (UpperCase(Token.Text) = '''CURRENT_TIMESTAMP''') then
      begin
        FieldDef.DefaultValue.AsAnsiString  := 'CURRENT_TIMESTAMP';
        FieldDef.newDefaultValue := True;
        GetCurrentToken;
        Exit;
      end;

      // 5.14 fix DEFAULT CURRENT_TIME problem
      if (UpperCase(Token.Text) = 'CURRENT_TIME') or
         (UpperCase(Token.Text) = '''CURRENT_TIME''') then
      begin
        FieldDef.DefaultValue.AsAnsiString  := 'CURRENT_TIME';
        FieldDef.newDefaultValue := True;
        GetCurrentToken;
        Exit;
      end;

      // 5.14 fix DEFAULT CURRENT_TIME problem
      if (UpperCase(Token.Text) = 'CURRENT_DATE') or
         (UpperCase(Token.Text) = '''CURRENT_DATE''') then
      begin
        FieldDef.DefaultValue.AsAnsiString  := 'CURRENT_DATE';
        FieldDef.newDefaultValue := True;
        GetCurrentToken;
        Exit;
      end;

      // 5.14 fix DEFAULT SYSDATE problem
      if (UpperCase(Token.Text) = 'SYSDATE') or
         (UpperCase(Token.Text) = '''SYSDATE''') then
      begin
        FieldDef.DefaultValue.AsAnsiString  := 'SYSDATE';
        FieldDef.newDefaultValue := True;
        GetCurrentToken;
        Exit;
      end;



      // 5.09 fix CREATE TABLE T (I STRING DEFAULT "A/B")
      if Token.TokenType = tktQuotedString then
      begin
        FieldDef.DefaultValue.AsWideString := '"' + Token.Text + '"';
        FieldDef.NewDefaultValue := True;
        GetCurrentToken;
        Exit;
      end;


      // 5.07 fix CREATE TABLE T (I INTEGER DEFAULT -1)
      // old code
      // GetNextToken([], 30297, ErrorGUnexpectedEndOfCommand);
      // new code

      expr := TABSExpression.Create;
      try
        expr.ParseForValueExpression(LQuery, LLex);
        GetCurrentToken;
        if expr.IsEmpty then
          raise EABSException.Create(30297, ErrorGParseParameterError,
                                     [Token.Text, Token.LineNum, Token.ColumnNum]);

        if FieldDef.newDefaultValue then
          raise EABSException.Create(30299, ErrorGDefaultValueReDeclared, [FieldDef.FieldName]);

        // Set Value
        FieldDef.newDefaultValue := True;
        if IsReservedWord(Token, rwNULL) then
          // DEFAULT = NULL ?
          FieldDef.DefaultValue.SetNull
        else
          begin
            try
              // if Token.TokenType =
              FieldDef.DefaultValue.AsWideString := expr.getvalue.AsWideString;
            except
              raise EABSException.Create(30297, ErrorGParseParameterError,
                                     [Token.Text, Token.LineNum, Token.ColumnNum]);
            end;
            // Cast Value to Token Type
            case Token.TokenType of
              tktInt:   FieldDef.DefaultValue.Cast(bftSignedInt64);
              tktFloat: FieldDef.DefaultValue.Cast(bftDouble);
            end;
          end;
        // 5.07 fix CREATE TABLE T (I INTEGER DEFAULT -1)
        // old code
        // GetNextToken([], 30298, ErrorGRightParenthesisOrCommaExpected);
        Result := True;
      finally
        expr.Free;
      end;
    end;
end;//ParseDefaultValue


//------------------------------------------------------------------------------
// parse MINVALUE value
//------------------------------------------------------------------------------
function TABSDDLTableManipulation.ParseMinValue(var FieldDef: TSQLFieldDef): Boolean;
begin
  Result := False;
  if (IsReservedWord(Token, rwMINVALUE)) then
    begin
      GetNextToken([], 30300, ErrorGUnexpectedEndOfCommand);
      FieldDef.MinValue.AsAnsiString := AnsiString(Token.Text);
      FieldDef.MinValue.Cast(bftSignedInt64);
      FieldDef.newMinValue := True;
      Result := True;
      GetNextToken;
    end
  else
  if (IsReservedWord(Token, rwNOMINVALUE)) then
    begin
      FieldDef.newMinValue := True;
      FieldDef.MinValue.SetNull;
      Result := True;
      GetNextToken;
    end
end;//ParseMinValue


//------------------------------------------------------------------------------
// parse MaxVALUE value
//------------------------------------------------------------------------------
function TABSDDLTableManipulation.ParseMaxValue(var FieldDef: TSQLFieldDef): Boolean;
begin
  Result := False;
  if (IsReservedWord(Token, rwMaxVALUE)) then
    begin
      GetNextToken([], 30300, ErrorGUnexpectedEndOfCommand);
      FieldDef.MaxValue.AsAnsiString := AnsiString(Token.Text);
      FieldDef.MaxValue.Cast(bftSignedInt64);
      FieldDef.newMaxValue := True;
      Result := True;
      GetNextToken;
    end
  else
  if (IsReservedWord(Token, rwNOMaxVALUE)) then
    begin
      FieldDef.newMaxValue := True;
      FieldDef.MaxValue.SetNull;
      Result := True;
      GetNextToken;
    end

end;//ParseMaxValue


//------------------------------------------------------------------------------
// parse fiald ... PRIMARY KEY
//------------------------------------------------------------------------------
function TABSDDLTableManipulation.ParseFieldPrimaryKey(var FieldDef: TSQLFieldDef): Boolean;
var
  TokenNo: Integer;
  n: Integer;
begin
  // PRIMARY [KEY] [ ASC | DESC ]  [ CASE | NOCASE ]
  Result := False;
  // PRIMARY
  if (Token.ReservedWord = rwPRIMARY) then
    begin
      TokenNo := LLex.GetCurrentTokenNo;
      GetNextToken;
      // [KEY] ?
      if (Token.ReservedWord <> rwKEY) then
       LLex.SetCurrentTokenNo(TokenNo, Token);

      n := Length(Indexes);
      SetLength(Indexes,n+1);
      Indexes[n].Unique := False;
      Indexes[n].Primary := True;
      SetLength(Indexes[n].IndexFields,1);
      Indexes[n].IndexFields[0].FieldName := FieldDef.FieldName;
      GetNextToken;
      Result := True;
    end;
end;//ParseFieldPrimaryKey


//------------------------------------------------------------------------------
// parse fiald ... UNIQUE
//------------------------------------------------------------------------------
function TABSDDLTableManipulation.ParseFieldUnique(var FieldDef: TSQLFieldDef): Boolean;
var n: Integer;
begin
  Result := False;
  // UNIQUE
  if (Token.ReservedWord = rwUNIQUE) then
    begin
      n := Length(Indexes);
      SetLength(Indexes,n+1);
      Indexes[n].Unique := True;
      Indexes[n].Primary := False;
      SetLength(Indexes[n].IndexFields,1);
      Indexes[n].IndexFields[0].FieldName := FieldDef.FieldName;
      GetNextToken;
      Result := True;
    end;
end;//ParseFieldUnique


//------------------------------------------------------------------------------
// parse Primary Key
//------------------------------------------------------------------------------
function TABSDDLTableManipulation.ParsePrimaryKey: boolean;
var
  n: Integer;
begin
  // PRIMARY [KEY] [indexname] (column1 [asc|desc] [case|nocase] [, column2...])
  Result := false;
  // PRIMARY
  if (Token.ReservedWord = rwPRIMARY) then
    begin
      GetNextToken;
      // KEY
      if (Token.ReservedWord = rwKEY) then
      GetNextToken;

      n := Length(Indexes);
      SetLength(Indexes,n+1);
      Indexes[n].Unique := False;
      Indexes[n].Primary := True;

      // indexname
      if (Token.TokenType <> tktLeftParenthesis) then
        begin
          Indexes[n].IndexName := Token.Text;
          GetNextToken;
        end;

      ParseIndexFields(n);

{
      // '('
      if (Token.TokenType <> tktLeftParenthesis) then
        raise EABSException.Create(30327, ErrorGLeftParenthesisExpected,
                                 [Token.Text, Token.LineNum, Token.ColumnNum]);

      repeat
        GetNextToken;
        // FieldName
        if Token.TokenType in [tktString, tktReservedWord] then
          FieldName := Token.Text
        else
          raise EABSException.Create(30244, ErrorGFieldNameExpected,
                                  [Token.Text, Token.LineNum, Token.ColumnNum]);

        GetNextToken;

        desc := false;
        nocase := false;
        // ASC, CASE ?
        repeat
            case Token.ReservedWord of
                rwASC:    desc:=false;
                rwDESC:   desc:=true;
                rwCASE:   nocase:=false;
                rwNOCASE: nocase:=true;
            else
              break;
            end;
            GetNextToken([], 30333, ErrorGUnexpectedEndOfCommand);
        until false;


        // add field to primary index
        i := Length(PrimaryKeyFields);
        SetLength(PrimaryKeyFields, i + 1);
        PrimaryKeyFields[i].FieldName := FieldName;
        PrimaryKeyFields[i].desc := desc;
        PrimaryKeyFields[i].nocase := nocase;


        // ')' or ','
        if not (Token.TokenType in [tktComma, tktRightParenthesis]) then
          raise EABSException.Create(30245, ErrorGRightParenthesisOrCommaExpected,
                                  [Token.Text, Token.LineNum, Token.ColumnNum]);
      until (Token.TokenType = tktRightParenthesis);

      // get next token
      GetNextToken;
}
      Result := true;
    end;
end;//ParsePrimaryKey


//------------------------------------------------------------------------------
// Parse Index
//------------------------------------------------------------------------------
function TABSDDLTableManipulation.ParseIndex: boolean;
var n: Integer;
begin

  //  [UNIQUE] [INDEX] [indexname] (column_name  [ ASC | DESC ]  [ CASE | NOCASE ] ,
  //  [, column_name...])
  Result := false;

  if (not IsReservedWord(Token, rwUNIQUE)) and
     (not IsReservedWord(Token, rwINDEX)) then Exit;

  n := Length(Indexes);
  SetLength(Indexes,n+1);
  Indexes[n].Unique := False;
  Indexes[n].Primary := False;

  // UNIQUE
  if IsReservedWord(Token, rwUNIQUE) then
    begin
      Indexes[n].Unique := true;
      GetNextToken([tktReservedWord], 30267, ErrorGIndexKeywordExpected);
    end;

  // INDEX
  if not IsReservedWord(Token, rwINDEX) then
    raise EABSException.Create(30268, ErrorGIndexKeywordExpected,
             [Token.Text, Token.LineNum, Token.ColumnNum]);

  // [Index_name]
  GetNextToken;
  if Token.TokenType <> tktLeftParenthesis then
    begin
      if not (Token.TokenType in [tktReservedWord,tktString,tktQuotedString,
                                  tktBracketedString]) then
        raise EABSException.Create(30269, ErrorGIndexNameExpected,
                                 [Token.Text, Token.LineNum, Token.ColumnNum]);
      Indexes[n].IndexName := Token.Text;
    end;
    
  GetNextToken;
  
  ParseIndexFields(n);

  Result := true;
end;//ParseIndex


//------------------------------------------------------------------------------
// ParseIndexField
//------------------------------------------------------------------------------
procedure TABSDDLTableManipulation.ParseIndexField(IndexNo: Integer);
var f: Integer;
begin
  f := Length(Indexes[IndexNo].IndexFields);
  SetLength(Indexes[IndexNo].IndexFields, f+1);
  with Indexes[IndexNo].IndexFields[f] do
   begin
    // Column name
    FieldName := Token.Text;
    // default ASC
    desc := false;
    // default CASE
    nocase := false;
    // asc, case
    repeat
      GetNextToken([], 30333, ErrorGUnexpectedEndOfCommand);
      case Token.ReservedWord of
        rwASC:    desc:=false;
        rwDESC:   desc:=true;
        rwCASE:   nocase:=false;
        rwNOCASE: nocase:=true;
        else
          break;
      end;
    until false;
   end;
end;//ParseIndexField


//------------------------------------------------------------------------------
// ParseIndexFields (f,f,f)
//------------------------------------------------------------------------------
procedure TABSDDLTableManipulation.ParseIndexFields(IndexNo: Integer);
begin
  // '('
  // column list...
  repeat
    GetNextToken([tktReservedWord,tktString,tktQuotedString,tktBracketedString],
                 30274, ErrorGFieldNameExpected);
    ParseIndexField(IndexNo);
  until Token.TokenType = tktRightParenthesis;
  // ')'
  GetNextToken;
end;//ParseIndexFields


//------------------------------------------------------------------------------
// go to the Next token and Parse Integer (Int64)
//------------------------------------------------------------------------------
function TABSDDLTableManipulation.ParseInteger: Int64;
begin
  GetNextToken([tktInt], 30292, ErrorGIntegerExpected);
  try
    Result := StrToInt64(Token.Text);
  except
    raise EABSException.Create(30291, ErrorGIntegerExpected,
                               [Token.Text, Token.LineNum, Token.ColumnNum]);
  end;
  //GetNextToken;
end;//ParseInteger


//------------------------------------------------------------------------------
// Create ABSTable object and fill ABSTable params
//------------------------------------------------------------------------------
procedure TABSDDLTableManipulation.CreateInternalABSTable(query: TDataSet;
               var table: TDataSet; var db: TObject; FillAdvFieldDefs: Boolean);
var
  i: Integer;
  t: TABSTable;
begin
  t := TABSTable.Create(query.Owner);
  table := t;
  db := nil;

  if (DatabaseName = '') then
    t.DatabaseName := TABSQuery(query).DatabaseName
  else
    begin
      db := TABSDatabase.Create(nil);
      TABSDatabase(db).DatabaseName := GetTemporaryName('ExternalDb');
      TABSDatabase(db).SessionName := TABSQuery(query).SessionName;
      TABSDatabase(db).DatabaseFileName := DatabaseName;
      TABSDatabase(db).DisableTempFiles := TABSQuery(query).Database.DisableTempFiles;
      TABSDatabase(db).SilentMode := True;
      TABSDatabase(db).Password := Password;
      t.DatabaseName := TABSDatabase(db).DatabaseName;
      t.SessionName := TABSQuery(query).SessionName;
    end;
  t.SessionName := TABSQuery(query).SessionName;

  // Table Name
  t.TableName := Self.TableName;

  // Memory
  t.InMemory := IntoMemory or TABSQuery(query).InMemory;

  if (FillAdvFieldDefs) then
    begin
      t.AdvFieldDefs.Clear;
      for i:=0 to SQLFieldDefs.Count-1 do
        FillAdvFieldDef(
                        TFieldDef( t.AdvFieldDefs.AddFieldDef ),
                        SQLFieldDefs[i]
                       );
    end;
end;//CreateInternalABSTable


//------------------------------------------------------------------------------
// Add Primary Key into ABSTable
//------------------------------------------------------------------------------
procedure TABSDDLTableManipulation.FillAdvFieldDef(
                                        AdvFieldDef: TFieldDef;
                                        SQLFieldDef: TSQLFieldDef
                                                  );
var
  fd: TABSAdvFieldDef;
begin
  fd := TABSAdvFieldDef(AdvFieldDef);

  fd.Name := SQLFieldDef.FieldName;

  // DataType
  if SQLFieldDef.newFieldType then
    fd.DataType := SQLFieldDef.FieldType;

  // Size
  if SQLFieldDef.newLength then
    fd.Size := SQLFieldDef.Length;

  // Required
  if SQLFieldDef.newRequired then
    fd.Required := SQLFieldDef.Required;

  // Autoinc settings
  if SQLFieldDef.newAutoincIncrement then
    fd.AutoincIncrement := SQLFieldDef.AutoincIncrement;
  if SQLFieldDef.newAutoincLastValue then
    fd.AutoincInitialValue := SQLFieldDef.AutoincLastValue;
  if SQLFieldDef.newAutoincMinValue then
    fd.AutoincMinValue  := SQLFieldDef.AutoincMinValue;
  if SQLFieldDef.newAutoincMaxValue then
    fd.AutoincMaxValue  := SQLFieldDef.AutoincMaxValue;
  if SQLFieldDef.newAutoincCycled then
    fd.AutoincCycled    := SQLFieldDef.AutoincCycled;

  // Blob settings
  if SQLFieldDef.newBlobBlockSize then
    fd.BlobBlockSize := SQLFieldDef.BlobBlockSize;
  if SQLFieldDef.newBlobCompressionAlgorithm then
    fd.BlobCompressionAlgorithm := TCompressionAlgorithm(SQLFieldDef.BlobCompressionAlgorithm);
  if SQLFieldDef.newBlobCompressionMode then
    fd.BlobCompressionMode := SQLFieldDef.BlobCompressionMode;

  // Default Value
  if SQLFieldDef.newDefaultValue then
    fd.DefaultValue.Assign(SQLFieldDef.DefaultValue);

  // MinValue
  if SQLFieldDef.newMinValue then
    fd.MinValue.Assign(SQLFieldDef.MinValue);
  // MaxValue
  if SQLFieldDef.newMaxValue then
    fd.MaxValue.Assign(SQLFieldDef.MaxValue);

end;//FillAdvFieldDef


//------------------------------------------------------------------------------
// Add Indexes to ABSTable
//------------------------------------------------------------------------------
procedure TABSDDLTableManipulation.AddIndexes(T: TDataSet);
var
  i:      Integer;
  iFields, iCaseInsFields, iDescFields, d, d1, d2:    string;
  pkName: string;
  n:      Integer;
  IOptions: TIndexOptions;
begin
  TABSTable(T).IndexDefs.Clear;
//  TABSTable(T).RestructureIndexDefs.Clear;
  for n:=0 to Length(Indexes)-1 do
    begin
      d:='';
      d1:='';
      d2:='';
      iFields:='';
      iCaseInsFields:='';
      iDescFields:='';
      if Indexes[n].Primary then pkName := AutoNameConstraintPKPreffix
      else if Indexes[n].Unique then pkName := AutoNameConstraintUniquePreffix
      else pkName := 'i';

      for i:=0 to Length(Indexes[n].IndexFields) - 1 do
        begin
         pkName := pkName + AutoNameSymbol + Indexes[n].IndexFields[i].FieldName;
         iFields := iFields + d + Indexes[n].IndexFields[i].FieldName;
         if Indexes[n].IndexFields[i].nocase then
          begin
           iCaseInsFields := iCaseInsFields + d1 + Indexes[n].IndexFields[i].FieldName;
           d1 := ',';
          end;
         if Indexes[n].IndexFields[i].desc then
          begin
           iDescFields := iDescFields + d2 + Indexes[n].IndexFields[i].FieldName;
           d2 := ',';
          end;
         d := ',';
        end;
      //IndexDefs.Clear;
      //pkName := GetTemporaryName(pkName);
      if Indexes[n].IndexName <> '' then pkName := Indexes[n].IndexName;
      iOptions := [];
      if Indexes[n].Unique then iOptions := iOptions + [ixUnique];
      if Indexes[n].Primary then iOptions := iOptions + [ixPrimary];

      with TABSTable(T).AdvIndexDefs.AddIndexDef do
        begin
          Name := pkName;
          Fields := iFields;
          Options := iOptions;
          DescFields := iDescFields;
          CaseInsFields := iCaseInsFields;
        end;
       
      with TABSTable(T).RestructureIndexDefs.AddIndexDef do
        begin
          Name := pkName;
          Fields := iFields;
          Options := iOptions;
          DescFields := iDescFields;
          CaseInsFields := iCaseInsFields;
        end;
    end;
end;//AddIndexes

{
//------------------------------------------------------------------------------
// Add Primary Key into ABSTable
//------------------------------------------------------------------------------
procedure TABSDDLTableManipulation.AddPrimaryKey(T: TDataSet);
var
  i:      Integer;
  s,d:    string;
  pkName: string;
begin
  Raise Exception.Create('aaa');
  if (Length(PrimaryKeyFields) <> 0) then
    begin
      d:='';
      s:='';
      pkName := AutoNameConstraintPKPreffix;
      for i:=0 to Length(PrimaryKeyFields) - 1 do
        begin
         pkName := pkName + AutoNameSymbol + PrimaryKeyFields[i].FieldName;
         s := s + d + PrimaryKeyFields[i].FieldName;
         d := ',';
        end;
      //IndexDefs.Clear;
      pkName := GetTemporaryName(pkName);


      TABSTable(T).IndexDefs.Add(pkName, s, [ixPrimary]);
      TABSTable(T).RestructureIndexDefs.Add(pkName, s, [ixPrimary]);

    end;
end;//AddPrimaryKey


//------------------------------------------------------------------------------
// Add UNIQUE constraint and index into ABSTable
//------------------------------------------------------------------------------
procedure TABSDDLTableManipulation.AddUnique(T: TDataSet);
var
  i:      Integer;
  uName: string;
begin
  for i:=0 to UniqueFields.Count-1 do
    begin
      uName := GetTemporaryName(AutoNameConstraintUniquePreffix +
                                AutoNameSymbol +
                                UniqueFields[i]);
      TABSTable(T).IndexDefs.Add(uName, UniqueFields[i], [ixUnique]);
      TABSTable(T).RestructureIndexDefs.Add(uName, UniqueFields[i], [ixUnique]);
    end;
end;//AddUnique
}


//------------------------------------------------------------------------------
// Delete PrimaryKey
//------------------------------------------------------------------------------
procedure TABSDDLTableManipulation.DeletePrimaryKey(T: TDataSet);
var
  i: integer;
  AT: TABSTable;

  procedure DeleteIndexDef(idxDefs: TABSAdvIndexDefs; index: integer);
  begin
    idxDefs.DeleteIndexDef(idxDefs[index].Name);
  end;

begin
  AT := TABSTable(T);

  if AT.IndexDefs.Count <> 0 then
    begin

      for i:=0 to AT.IndexDefs.Count-1 do
        if ixPrimary in AT.IndexDefs[i].Options then
          begin
           DeleteIndexDef(AT.AdvIndexDefs, i);
           break;
          end;

      for i:=0 to AT.RestructureIndexDefs.Count-1 do
        if ixPrimary in AT.IndexDefs[i].Options then
          begin
            DeleteIndexDef(AT.RestructureIndexDefs, i);
            break;
          end;

    end;//if

end;//DeletePrimaryKey


//------------------------------------------------------------------------------
// parses query
//------------------------------------------------------------------------------
procedure TABSDDLTableManipulation.Parse;
begin
  // check for not expected token
  if (LLex.LookNextToken(Token)) then
     raise EABSException.Create(30246, ErrorGUnexpectedToken,
                                [Token.Text, Token.LineNum, Token.ColumnNum]);
end;//Parse




////////////////////////////////////////////////////////////////////////////////
//
// TABSDDLCreateTable
//
////////////////////////////////////////////////////////////////////////////////


//------------------------------------------------------------------------------
// Create Table
//------------------------------------------------------------------------------
procedure TABSDDLCreateTable.CreateTable(query: TDataSet);
var
  T: TABSTable;
  db: TABSDatabase;
begin
  CreateInternalABSTable(query, TDataset(T), TObject(db), True);
  try
    if T.Exists then
     begin
      if FCreateIfNotExists then
       Exit
      else
       raise EABSException.Create(30247, ErrorGTableAlreadyExists, [T.TableName]);
     end;
    try
      AddIndexes(T);
      T.CreateTable;
    except
      on e:Exception do
        raise EABSException.Create(30248, ErrorGErrorCreatingTable, [e.Message]);
    end;

  finally
    T.Free;
    if (db <> nil) then
      db.Free;
  end
end;//CreateTable


//------------------------------------------------------------------------------
// parses query
//------------------------------------------------------------------------------
procedure TABSDDLCreateTable.Parse;
begin
  if (not GetNextToken) then
    raise EABSException.Create(30249, ErrorGBlankSqlCommand);

  FCreateIfNotExists := False;
  if IsReservedWord(Token, rwIF) then
   begin
    GetNextToken;
    if IsReservedWord(Token, rwNOT) then
      GetNextToken
    else
      raise EABSException.Create(30486, ErrorGOtherTokenExpected,
                          ['NOT',Token.Text, Token.LineNum, Token.ColumnNum]);
    if IsReservedWord(Token, rwEXISTS) then
      GetNextToken
    else
      raise EABSException.Create(30487, ErrorGOtherTokenExpected,
                          ['EXISTS',Token.Text, Token.LineNum, Token.ColumnNum]);
    FCreateIfNotExists := True;
   end;

  ParseTableNameToken;
  ParseTableElementList;
  inherited;
end;//Parse


//------------------------------------------------------------------------------
// executes query
//------------------------------------------------------------------------------
procedure TABSDDLCreateTable.ExecSQL(query: TDataset; IsRoot, RequestLive: boolean;
                            var ReadOnly: boolean; ParentQueryAO: TABSAO = nil;
                            ParentCursor: TABSCursor = nil);
begin
  CreateTable(query);
end;//ExecSQL



////////////////////////////////////////////////////////////////////////////////
//
// TABSDDLDropTable
//
////////////////////////////////////////////////////////////////////////////////


//------------------------------------------------------------------------------
// Drop Table
//------------------------------------------------------------------------------
procedure TABSDDLDropTable.DropTable(query: TDataSet);
var
  T: TABSTable;
  db: TABSDatabase;
begin
  CreateInternalABSTable(query, TDataset(T), TObject(db), True);
  try
    try
      if (T.Exists) then
        T.DeleteTable;
    except
      on e:Exception do
        raise EABSException.Create(30250, ErrorGDroppingTable, [e.Message]);
    end;

  finally
    T.Free;
    if (db <> nil) then
      db.Free;
  end
end;//DropTable


//------------------------------------------------------------------------------
// parse query
//------------------------------------------------------------------------------
procedure TABSDDLDropTable.Parse;
begin
  if (not GetNextToken) then
    raise EABSException.Create(30251,ErrorGBlankSQLCommand);

  if IsReservedWord(Token, rwIF) then
   begin
    GetNextToken;
    if IsReservedWord(Token, rwEXISTS) then
      GetNextToken
    else
      raise EABSException.Create(30485, ErrorGOtherTokenExpected,
                          ['EXISTS',Token.Text, Token.LineNum, Token.ColumnNum]);
   end;
  ParseTableNameToken;
  inherited;
end;//Parse


//------------------------------------------------------------------------------
// executes query
//------------------------------------------------------------------------------
procedure TABSDDLDropTable.ExecSQL(query: TDataset; IsRoot, RequestLive: boolean;
                            var ReadOnly: boolean; ParentQueryAO: TABSAO = nil;
                            ParentCursor: TABSCursor = nil);
begin
  DropTable(query);
end;//ExecSQL



////////////////////////////////////////////////////////////////////////////////
//
// TABSDDLTruncateTable
//
////////////////////////////////////////////////////////////////////////////////


//------------------------------------------------------------------------------
// parse query
//------------------------------------------------------------------------------
procedure TABSDDLTruncateTable.Parse;
begin
  if (not GetNextToken) then
    raise EABSException.Create(30490,ErrorGBlankSQLCommand);

  if IsReservedWord(Token, rwTABLE) then
    GetNextToken;

  ParseTableNameToken;
  inherited;
end;//Parse


//------------------------------------------------------------------------------
// TruncateTable
//------------------------------------------------------------------------------
procedure TABSDDLTruncateTable.TruncateTable(query: TDataSet);
var
  T: TABSTable;
  db: TABSDatabase;
begin
  CreateInternalABSTable(query, TDataset(T), TObject(db), True);
  try
  
    try
//      if (T.Exists) then
        T.EmptyTable;
    except
      on e:Exception do
        raise EABSException.Create(30491, ErrorGTruncatingTable, [e.Message]);
    end;

  finally
    T.Free;
    if (db <> nil) then
      db.Free;
  end
end;//TruncateTable


//------------------------------------------------------------------------------
// ExecSQL
//------------------------------------------------------------------------------
procedure TABSDDLTruncateTable.ExecSQL(query: TDataset; IsRoot,
  RequestLive: boolean; var ReadOnly: boolean; ParentQueryAO: TABSAO;
  ParentCursor: TABSCursor);
begin
  TruncateTable(query);
end;//ExecSQL



////////////////////////////////////////////////////////////////////////////////
//
// TABSDDLAlterTable
//
////////////////////////////////////////////////////////////////////////////////


//------------------------------------------------------------------------------
// Constructor
//------------------------------------------------------------------------------
constructor TABSDDLAlterTable.Create(Lexer: TABSLexer; Query: TDataSet);
begin
  NewPassword := '';
  NewEncrypted := false;
  DropColumnNamesList := TStringList.Create;
  RenameColumnOldNamesList := TStringList.Create;
  RenameColumnNewNamesList := TStringList.Create;
  NewTableName := '';
  inherited Create(Lexer, Query);
end;//Create


//------------------------------------------------------------------------------
// Destructor
//------------------------------------------------------------------------------
destructor TABSDDLAlterTable.Destroy;
begin
  DropColumnNamesList.Free;
  RenameColumnOldNamesList.Free;
  RenameColumnNewNamesList.Free;
  inherited;
end;//Destroy


//------------------------------------------------------------------------------
// parse AlterType token
//------------------------------------------------------------------------------
procedure TABSDDLAlterTable.ParseAlterTypeToken;
begin
  if Token.TokenType = tktReservedWord then
    case Token.ReservedWord of
     rwADD:    AlterType := atAdd;
     rwDROP:   AlterType := atDrop;
     rwMODIFY: AlterType := atModify;
     rwRENAME:
               begin
                 // RENAME TO | RENAME [COLUMN] columnname TO
                 GetNextToken;
                 if IsReservedWord(Token, rwTO) then
                   AlterType := atRenameTable
                 else
                   begin
                     AlterType := atRenameColumn;
                     if IsReservedWord(Token, rwCOLUMN) then
                       GetNextToken;
                     Exit;
                   end;
               end;
     else
       raise EABSException.Create(30252, ErrorGAddOrDropOrModifyKeywordExpected,
                                 [Token.Text, Token.LineNum, Token.ColumnNum]);
    end//case
  else
    raise EABSException.Create(30253, ErrorGAddOrDropOrModifyKeywordExpected,
                            [Token.Text, Token.LineNum, Token.ColumnNum]);
  GetNextToken;
end;//ParseAlterTypeToken


//------------------------------------------------------------------------------
// parse NewPassword token
//------------------------------------------------------------------------------
procedure TABSDDLAlterTable.ParseNewPasswordToken;
begin
  GetNextToken([tktReservedWord], 30255, ErrorGQuotedPasswordDataExpected);
  if Token.ReservedWord <> rwPASSWORD then exit;
  GetNextToken([tktReservedWord, tktQuotedString, tktParameter], 30256, ErrorGQuotedPasswordDataExpected);
  if (Token.TokenType <> tktParameter) then
    NewPassword := Token.Text
  else
    NewPassword := Token.ParamValue.AsAnsiString;
  NewEncrypted := true;
  GetNextToken([tktComma, tktRightParenthesis],
               30257, ErrorGRightParenthesisOrCommaExpected);
end;//ParseNewPasswordToken


//------------------------------------------------------------------------------
// Alter Table DropColumn
//------------------------------------------------------------------------------
procedure TABSDDLAlterTable.ParseRenameColumnsList;
begin
  // OldColumnName
  RenameColumnOldNamesList.Add(Token.Text);
  GetNextToken;

  // TO
  if not IsReservedWord(Token, rwTO) then
    raise EABSException.Create(30352, ErrorGOtherTokenExpected,
                          ['TO', Token.Text, Token.LineNum, Token.ColumnNum]);
  GetNextToken;

  // NewColumnName
  RenameColumnNewNamesList.Add(Token.Text);
  GetNextToken;
end;//ParseRenameColumnsList


//------------------------------------------------------------------------------
// Alter Table DropColumn
//------------------------------------------------------------------------------
procedure TABSDDLAlterTable.DropColumn(T: TDataSet);
var i: Integer;
    begin
  for i:=0 to DropColumnNamesList.Count-1 do
    TABSTable(T).RestructureFieldDefs.DeleteFieldDef(DropColumnNamesList[i]);
end;//DropColumn


//------------------------------------------------------------------------------
// Alter Table AddColumn
//------------------------------------------------------------------------------
procedure TABSDDLAlterTable.AddColumn(T: TDataSet);
var
  i: Integer;
  fd: TABSAdvFieldDef;
begin
    for i:=0 to SQLFieldDefs.Count-1 do
      begin
        fd := TABSTable(T).RestructureFieldDefs.Find(SQLFieldDefs[i].FieldName);
        if fd <> nil then
         begin
          if SQLFieldDefs[i].CreateIfNotExists then
            Continue
          else
            raise EABSException.Create(30351, ErrorGFieldAlreadyExists,
                                       [SQLFieldDefs[i].FieldName]);
         end;
        // Add Field
        fd := TABSTable(T).RestructureFieldDefs.AddFieldDef;
        // Fill Field
        FillAdvFieldDef(TFieldDef(fd), SQLFieldDefs[i]);
      end;
  //DeletePrimaryKey(T);
  AddIndexes(T);
end;//AddColumn


//------------------------------------------------------------------------------
// Alter Table Modify
//------------------------------------------------------------------------------
procedure TABSDDLAlterTable.Modify(T: TDataSet);
var
  i: Integer;
  fd: TABSAdvFieldDef;
begin
    for i:=0 to SQLFieldDefs.Count-1 do
      begin
        fd := TABSTable(T).RestructureFieldDefs.Find(SQLFieldDefs[i].FieldName);
        if fd = nil then
          raise EABSException.Create(30259, ErrorGCannotFindField,
                                     [SQLFieldDefs[i].FieldName]);

        // Apply Changes
        FillAdvFieldDef(TFieldDef(fd), SQLFieldDefs[i]);

      end;
  //DeletePrimaryKey(T);
  AddIndexes(T);
end;//Modify


//------------------------------------------------------------------------------
// Rename Column
//------------------------------------------------------------------------------
procedure TABSDDLAlterTable.RenameColumn(T: TDataSet);
var  i: Integer;
begin
  for i:=0 to  RenameColumnOldNamesList.Count-1 do
   TABSTable(T).RenameField(RenameColumnOldNamesList[i],
                            RenameColumnNewNamesList[i]);
end;//RenameColumn


//------------------------------------------------------------------------------
// parse query
//------------------------------------------------------------------------------
procedure TABSDDLAlterTable.Parse;
begin
  if (not GetNextToken) then
    raise EABSException.Create(30261, ErrorGBlankSqlCommand);

  // Parse Table Name
  ParseTableNameToken;

  // Parse Alter Table Type
  ParseAlterTypeToken;

  case AlterType of
   atAdd:
  ParseTableElementList;
   atModify:
     ParseTableElementList;
   atDrop:
      begin
       GetNextToken;
       ParseColumnList(DropColumnNamesList);
      end;
   atRenameColumn:
     ParseRenameColumnsList;
   atRenameTable:
                  begin
       NewTableName := Token.Text;
                  end;
   else
     raise EABSException.Create(30350, ErrorGNotImplementedYet);
      end;

end;//Parse


//------------------------------------------------------------------------------
// executes query
//------------------------------------------------------------------------------
procedure TABSDDLAlterTable.ExecSQL(query: TDataset; IsRoot, RequestLive: boolean;
                             var ReadOnly: boolean; ParentQueryAO: TABSAO = nil;
                             ParentCursor: TABSCursor = nil);
var
  T: TABSTable;
  db: TABSDatabase;
  Log: string;
begin
  CreateInternalABSTable(query, TDataset(T), TObject(db), False);
  try
    try
      // get RestructureFieldDefs
      t.Open;
      t.Close;

      case AlterType of
        atDrop:    DropColumn(T);
        atAdd:     AddColumn(T);
        atModify:  Modify(T);
        atRenameColumn: RenameColumn(T);
        atRenameTable:  T.RenameTable(NewTableName);
      end;

      if ( AlterType in [atDrop, atAdd, atModify] ) then
        // RestructureTable
      if not (t.RestructureTable(Log)) then
        raise EABSException.Create(30347 ,ErrorGrestructureTableError, [Log]);

    except
      on e:Exception do
        raise EABSException.Create(30265, ErrorGErrorAlteringTable,[e.Message]);
    end
  finally
    T.Free;
    if (db <> nil) then
      db.Free;
  end
end;//ExecSQL




////////////////////////////////////////////////////////////////////////////////
//
// TABSDDLRenameTable
//
////////////////////////////////////////////////////////////////////////////////


//------------------------------------------------------------------------------
// parse query
//------------------------------------------------------------------------------
procedure TABSDDLRenameTable.Parse;
begin
  if (not GetCurrentToken) then
    raise EABSException.Create(30354, ErrorGBlankSqlCommand);

  AlterType := atRenameTable;

  // Parse Table Name
  ParseTableNameToken;

  // TO
  if not IsReservedWord(Token, rwTO) then
    raise EABSException.Create(30355, ErrorGOtherTokenExpected,
                          ['TO', Token.Text, Token.LineNum, Token.ColumnNum]);
  GetNextToken;

  // New Table Name
  NewTableName := Token.Text;

end;//Parse



////////////////////////////////////////////////////////////////////////////////
//
// TABSDDLCreateIndex
//
////////////////////////////////////////////////////////////////////////////////


//------------------------------------------------------------------------------
// Constructor
//------------------------------------------------------------------------------
constructor TABSDDLCreateIndex.Create(Lexer: TABSLexer; Query: TDataSet);
begin
  Unique := False;
  SetLength(ABSIndexFields, 0);
  inherited Create(Lexer, Query);
end;//Create


//------------------------------------------------------------------------------
// parse query
//------------------------------------------------------------------------------
procedure TABSDDLCreateIndex.Parse;
begin

  if (not GetCurrentToken) then
    raise EABSException.Create(30266, ErrorGBlankSQLCommand);

  // UNIQUE
  if IsReservedWord(Token, rwUNIQUE) then
    begin
      Unique:=true;
      GetNextToken([tktReservedWord], 30267, ErrorGIndexKeywordExpected);
    end;

  // INDEX
  if not IsReservedWord(Token, rwINDEX) then
    raise EABSException.Create(30268, ErrorGIndexKeywordExpected,
             [Token.Text, Token.LineNum, Token.ColumnNum]);

  // Index_name
  GetNextToken([tktReservedWord,tktString,tktQuotedString,tktBracketedString],
               30269, ErrorGIndexNameExpected);
  IndexName := Token.Text;

  // ON
  GetNextToken([tktReservedWord], 30270, ErrorGOnKeywordExpected);
  if (not IsReservedWord(Token, rwON)) then
    raise EABSException.Create(30271, ErrorGOnKeywordExpected,
                 [Token.Text, Token.LineNum, Token.ColumnNum]);
                 
  // table_name
  GetNextToken;
  ParseTableNameToken(DatabaseFileName, TableName, TableAlias, Password, InMemory);

  // '('
  if (Token.TokenType <> tktLeftParenthesis) then
    raise EABSException.Create(30273, ErrorGLeftParenthesisExpected);
  // column list...
  repeat
    GetNextToken([tktReservedWord,tktString,tktQuotedString,tktBracketedString],
                 30274, ErrorGFieldNameExpected);
    // Password
    if IsReservedWord(Token, rwPassword) then
      begin
        GetNextToken([tktQuotedString, tktParameter], 30275, ErrorGQuotedPasswordDataExpected);
        if (Token.TokenType <> tktParameter) then
          Password := Token.Text
        else
          Password := string(Token.ParamValue.AsAnsiString);
        GetNextToken([tktComma, tktRightParenthesis],
                     30276, ErrorGRightParenthesisOrCommaExpected);
      end
    else
      begin
        SetLength(ABSIndexFields, Length(ABSIndexFields)+1);
        with ABSIndexFields[Length(ABSIndexFields)-1] do
          begin
            // Column name
            FieldName := Token.Text;
            // default ASC
            desc := false;
            // default CASE
            nocase := false;
            repeat
              GetNextToken([], 30277, ErrorGUnexpectedEndOfCommand);
              case Token.ReservedWord of
                rwASC:    desc:=false;
                rwDESC:   desc:=true;
                rwCASE:   nocase:=false;
                rwNOCASE: nocase:=true;
              end;
            until Token.TokenType in [tktComma, tktRightParenthesis];
          end;
      end;
  until Token.TokenType = tktRightParenthesis;

//  // check for not expected token
//  if (LLex.LookNextToken(Token)) then
//    raise ETblException.Create(02050,
//                [Token.Text, Token.LineNum, Token.ColumnNum]);
end;//Parse


//------------------------------------------------------------------------------
// execute query
//------------------------------------------------------------------------------
procedure TABSDDLCreateIndex.ExecSQL(query: TDataset; IsRoot, RequestLive: boolean;
                            var ReadOnly: boolean; ParentQueryAO: TABSAO = nil;
                            ParentCursor: TABSCursor = nil);
var
   T: TABSTable;
   DB: TABSDatabase;
   Fields, DescFields, CaseInsFields: string;
   delim1,delim2,delim3: string;
   i: Integer;
   Options: TIndexOptions;
begin
   Fields:=''; DescFields:=''; CaseInsFields:='';
   delim1:=''; delim2:=''; delim3:='';
   for i:=0 to Length(ABSIndexFields)-1 do
     begin
       Fields := Fields + delim1 + ABSIndexFields[i].FieldName;
       delim1 := ';';
       if ABSIndexFields[i].desc then
         begin
           DescFields := DescFields + delim2 + ABSIndexFields[i].FieldName;
           delim2 := ';';
         end;
       if ABSIndexFields[i].nocase then
         begin
           CaseInsFields := CaseInsFields + delim3 + ABSIndexFields[i].FieldName;
           delim3 := ';';
         end;
     end;
   // Index Options
   if Unique then
     Options:=[ixUnique]
   else
     Options:=[];

   CreateDbAndTableComponents(query,DatabaseFileName,TableName,Password,InMemory,TDataset(T),TObject(DB));
   try
     T.Open;
     T.AddIndex(IndexName, Fields, Options, DescFields, CaseInsFields);
     T.Close;
   finally
     FreeDbAndTableComponents(T,DB);
   end;
end;//ExecSQL




////////////////////////////////////////////////////////////////////////////////
//
// TABSDDLDropIndex
//
////////////////////////////////////////////////////////////////////////////////


//------------------------------------------------------------------------------
// Constructor
//------------------------------------------------------------------------------
constructor TABSDDLDropIndex.Create(Lexer: TABSLexer; Query: TDataSet);
begin
  Password:='';
  inherited Create(Lexer, Query);
end;//Create


//------------------------------------------------------------------------------
// parse query
//------------------------------------------------------------------------------
procedure TABSDDLDropIndex.Parse;
begin
  if (not GetNextToken) then
    raise EABSException.Create(30278, ErrorGUnexpectedEndOfCommand,
                               [Token.LineNum, Token.ColumnNum]);

  // table_name
  ParseTableNameToken(DatabaseFileName, TableName, TableAlias, Password, InMemory);

  // '.'
  if (Token.TokenType <> tktDot) then
    begin
      IndexName := TableName;
      TableName := DatabaseFileName;
      DatabaseFileName := '';
    end
  else
    begin
      // index_name
      if (not GetNextToken) then
        raise EABSException.Create(30281, ErrorGUnexpectedEndOfCommand,
                                   [Token.LineNum, Token.ColumnNum]);
      IndexName:=Token.Text;
      GetNextToken;
    end;

  if (TableName = '') then
    raise EABSException.Create(30498, ErrorGTableNameExpected ,
                                   [IndexName, Token.LineNum, Token.ColumnNum]);

  // password
  if IsReservedWord(Token, rwPassword) then
      begin
        GetNextToken([tktQuotedString, tktParameter], 30282, ErrorGQuotedPasswordDataExpected);
        if (Token.TokenType <> tktParameter) then
          Password := Token.Text
        else
          Password := string(Token.ParamValue.AsAnsiString);
      end;

end;//Parse


//------------------------------------------------------------------------------
// execute query
//------------------------------------------------------------------------------
procedure TABSDDLDropIndex.ExecSQL(query: TDataset; IsRoot, RequestLive: boolean;
                            var ReadOnly: boolean; ParentQueryAO: TABSAO = nil;
                            ParentCursor: TABSCursor = nil);
var
   T: TABSTable;
   DB: TABSDatabase;
begin
  CreateDbAndTableComponents(query,DatabaseFileName,TableName,Password,InMemory,TDataset(T),TObject(DB));
  try
    T.Open;
    T.DeleteIndex(IndexName);
    T.Close;
  finally
    FreeDbAndTableComponents(T,DB);
  end;
end;//ExecSQL



////////////////////////////////////////////////////////////////////////////////
//
// TABSSQLStartTransaction
//
////////////////////////////////////////////////////////////////////////////////


//------------------------------------------------------------------------------
// parse query
//------------------------------------------------------------------------------
procedure TABSSQLStartTransaction.Parse;
begin
  if (not GetCurrentToken) then
    raise EABSException.Create(30457, ErrorGBlankSqlCommand);

  // START
  if not IsReservedWord(Token, rwSTART) then
    raise EABSException.Create(30458, ErrorGOtherTokenExpected,
                               ['START', Token.Text, Token.LineNum, Token.ColumnNum]);

  if (not GetNextToken) then
    raise EABSException.Create(30459,ErrorGUnexpectedEndOfCommand,
                               [Token.LineNum, Token.ColumnNum]);

  // TRANSACTION
  if not IsReservedWord(Token, rwTRANSACTION) then
    raise EABSException.Create(30460, ErrorGOtherTokenExpected,
                               ['TRANSACTION', Token.Text, Token.LineNum, Token.ColumnNum]);

  //inherited;
end;//Parse


//------------------------------------------------------------------------------
// execute query
//------------------------------------------------------------------------------
procedure TABSSQLStartTransaction.ExecSQL(query: TDataset; IsRoot,
  RequestLive: boolean; var ReadOnly: boolean; ParentQueryAO: TABSAO = nil;
  ParentCursor: TABSCursor = nil);
begin
  TABSQuery(query).database.StartTransaction;
end;//ExecSQL



////////////////////////////////////////////////////////////////////////////////
//
// TABSSQLCommit
//
////////////////////////////////////////////////////////////////////////////////


//------------------------------------------------------------------------------
// parse query
//------------------------------------------------------------------------------
procedure TABSSQLCommit.Parse;
begin
  // COMMIT [WORK] [FLUSH]
  FFlush := False;

  if (not GetCurrentToken) then
    raise EABSException.Create(30461, ErrorGBlankSqlCommand);

  // COMMIT
  if not IsReservedWord(Token, rwCOMMIT) then
    raise EABSException.Create(30462, ErrorGOtherTokenExpected,
                               ['COMMIT', Token.Text, Token.LineNum, Token.ColumnNum]);

  while (GetNextToken) do
    if IsReservedWord(Token, rwFLUSH) then FFlush := True;
   
end;//Parse


//------------------------------------------------------------------------------
// execute query
//------------------------------------------------------------------------------
procedure TABSSQLCommit.ExecSQL(query: TDataset; IsRoot,
  RequestLive: boolean; var ReadOnly: boolean; ParentQueryAO: TABSAO = nil;
  ParentCursor: TABSCursor = nil);
begin
  TABSQuery(query).database.Commit(FFlush);
end;//ExecSQL



////////////////////////////////////////////////////////////////////////////////
//
// TABSSQLRollback
//
////////////////////////////////////////////////////////////////////////////////


//------------------------------------------------------------------------------
// parse query
//------------------------------------------------------------------------------
procedure TABSSQLRollback.Parse;
begin
  // ROLLBACK [WORK]

  if (not GetCurrentToken) then
    raise EABSException.Create(30463, ErrorGBlankSqlCommand);

  // ROLLBACK
  if not IsReservedWord(Token, rwROLLBACK) then
    raise EABSException.Create(30464, ErrorGOtherTokenExpected,
                               ['ROLLBACK', Token.Text, Token.LineNum, Token.ColumnNum]);

  //while (GetNextToken) do ;
   
end;//Parse


//------------------------------------------------------------------------------
// execute query
//------------------------------------------------------------------------------
procedure TABSSQLRollback.ExecSQL(query: TDataset; IsRoot,
  RequestLive: boolean; var ReadOnly: boolean; ParentQueryAO: TABSAO = nil;
  ParentCursor: TABSCursor = nil);
begin
  TABSQuery(query).database.Rollback;
end;//ExecSQL



////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////


//------------------------------------------------------------------------------
// set field value from ABSVariant
//------------------------------------------------------------------------------
procedure SetABSVariantIntoField(Value: TABSVariant; Field: TField);
var
  s: AnsiString;
begin
  if Value = nil then
    raise EABSException.Create(30359, ErrorGValueIsNull);

  if (Value.IsNull) then
    Field.Clear
  else
    case Field.DataType of
      ftBlob,
      ftGraphic:
        begin
{$IFDEF D12H}
          // 6.0 bug fix to assign unicode string correctly to widememo field
          if (Value.IsStringDataType) then
            if (Value.IsWideStringDataType) then
              Field.AsWideString := Value.AsWideString
            else
              Field.AsString := Value.AsAnsiString
          else
{$ENDIF}
            begin
              SetLength(s, Value.DataSize);
              Value.CopyDataToAddress(PAnsiChar(s));
              Field.AsString := s;
            end;
        end;
      ftMemo,
      ftFmtMemo:
        begin
{$IFDEF D12H}
          // 6.07 bug fix to assign unicode string correctly to widememo field
          if (Value.IsStringDataType) then
            if (Value.IsWideStringDataType) then
              Field.AsWideString := Value.AsWideString
            else
              Field.AsString := Value.AsAnsiString
          else
{$ENDIF}
           begin
            SetLength(s, Value.DataSize);
            Value.CopyDataToAddress(PAnsiChar(s));
            Field.AsString := s;
           end;
        end;
      ftLargeint:
        begin
          TLargeintField(Field).Value := Value.AsInt64;
        end;
      ftInteger:
        begin
          Field.AsInteger := Value.AsInteger;
        end;
      ftDate:
        begin
         Field.AsDateTime := Value.AsTDate;
        end;
      ftTime:
        begin
         Field.AsDateTime := Value.AsTTime;
        end;
      ftDateTime:
        begin
         Field.AsDateTime := Value.AsTDateTime;
        end;
{$IFDEF D6H}
      ftTimeStamp:
        begin
          TSQLTimeStampField(Field).AsDateTime := Value.AsTDateTime;
        end;
{$ENDIF}
      else
        Field.Value := Value.AsVariant;
    end;
end;//SetABSVariantIntoField


end.


