{   $Id: interbase.pp,v 1.1.2.1 2000/12/02 15:07:42 michael Exp $     
    
    Copyright (c) 2000 by Pavel Stingl


    Interbase database & dataset
    
    See the file COPYING.FPC, included in this distribution,
    for details about the copyright.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

 **********************************************************************}

unit Interbase;

{$H+}

interface

uses SysUtils, Classes, IBase60, DB;

type

  PInteger = ^integer;
  PSmallInt= ^smallint;
  
  TIBDatabase = class;
  TIBTransaction = class;
  TIBQuery = class;
  TIBStoredProc = class;
    
{ TIBDatabase }
  
  TIBDatabase = class (TDatabase)
  private
    FIBDatabaseHandle    : pointer;
    FPassword            : string;
    FStatus              : array [0..19] of ISC_STATUS;
    FTransaction         : TIBTransaction;
    FUserName            : string;
    FDialect             : integer;
    
    procedure SetDBDialect;
    procedure SetTransaction(Value : TIBTransaction);
  protected
    function GetHandle : pointer; virtual;
      { This procedure makes connection to Interbase server internally.
        Is visible only by descendants, in application programming
        will be invisible. Connection you must establish by setting 
        @link(Connected) property to true, or by call of Open method.
      }
    procedure DoInternalConnect; override;
      { This procedure disconnects object from IB server internally.
        Is visible only by descendants, in application programming
        will be invisible. Disconnection you must make by setting 
        @link(Connected) property to false, or by call of Close method.
      }
    procedure DoInternalDisconnect; override;
  public
    procedure StartTransaction; override;
    procedure EndTransaction; override;
    constructor Create(AOwner : TComponent); override;
    destructor Destroy; override;
  published
    { On connect, TIBDatabase object retrieve SQL dialect of database file,
      and sets this property to responding value }
    property Dialect  : integer read FDialect write FDialect;
    { Before firing Open method you must set @link(Password),@link(DatabaseName),
      @link(UserName) properties in order of successfull connect to database }
    property Password : string read FPassword write FPassword;
    { This property holds default transaction for database. You must assign it by hand
      now, default assignment becomes handy, in next release, with transaction
      handling and evidence }
    property Transaction : TIBTransaction read FTransaction write SetTransaction;
    { Before firing Open method you must set @link(Password),@link(DatabaseName),
      @link(UserName) properties in order of successfull connect to database }
    property UserName : string read FUserName write FUserName;

    { Identifies, if connection to Interbase server is established, or not.
      Instead of calling Open, Close methods you can connect or disconnect
      by setting this property to true or false.
    }
    property Connected;
    { This property holds database connect string. On local server it will be
      absolute path to the db file, if you wanna connect over network, this
      path looks like this: <server_name>:<path_on_server>, where server_name
      is absolute IP address, or name of server in DNS or hosts file, path_on_server
      is absolute path to the file again }
    property DatabaseName;
    property KeepConnection;
    property LoginPrompt;
    property Params;
    property OnLogin;
  end;

{ TIBTransaction }

  {
    Interbase has two modes for commit and rollback transactions,
    the difference is simple. If you execute Commit or Rollback,
    current transaction ends, and you must create new one.
    If you, on other side, need only commit or rollback data
    without transaction closing, execute with CommitRetaining or
    RollbackRetaining. Transaction handle, environment etc. will be
    as same as before action. Possible values are : caNone, caCommit, caCommitRetaining, caRollback, 
    caRollbackRetaining
  }
  
  TCommitRollbackAction = (caNone, caCommit, caCommitRetaining, caRollback, 
    caRollbackRetaining);
  TAccessMode = (amReadWrite, amReadOnly);
  TIsolationLevel = (ilConcurrent, ilConsistent, ilReadCommittedRecV,
    ilReadCommitted);
  TLockResolution = (lrWait, lrNoWait);
  TTableReservation = (trNone, trSharedLockRead, trSharedLockWrite, 
    trProtectedLockRead, trProtectedLockWrite);
  
  TIBTransaction = class (TComponent)
  private
    FTransactionHandle   : pointer;               // Transaction handle
    FAction              : TCommitRollbackAction; 
    FActive              : boolean;
    FTPB                 : string;                // Transaction parameter buffer
    FDatabase            : TIBDatabase;
    FAccessMode          : TAccessMode;
    FIsolationLevel      : TIsolationLevel;
    FLockResolution      : TLockResolution;
    FTableReservation    : TTableReservation; 
    FStatus              : array [0..19] of ISC_STATUS;
    
    procedure SetActive(Value : boolean);
    procedure SetTPB;
  protected
    function GetHandle : pointer; virtual;
  public
    { Commits all actions, which was made in transaction, and closes transaction}
    procedure Commit; virtual;
    { Commits all actions, closes transaction, and creates new one }
    procedure CommitRetaining; virtual;
    { Rollbacks all actions made in transaction, and closes transaction }
    procedure Rollback; virtual;
    { Rollbacks all actions made in transaction, closes trans. and creates new one }
    procedure RollbackRetaining; virtual;
    { Creates new transaction. If transaction is active, closes it and make new one.
      Action taken while closing responds to @link(Action) property settings }
    procedure StartTransaction;
    constructor Create(AOwner : TComponent); override;
    destructor Destroy; override;
  published
    { Default action while closing transaction by setting 
     @link(Active) property. For details see @link(TCommitRollbackAction)}
    property Action : TCommitRollbackAction read FAction write FAction;
    { Is set to true while transaction is active, false if not.
      If you set it manually to true, object executes 
      @link(StartTransaction) method, if transaction is
      active, and you set Active to false, object executes
      one of @link(Commit), @link(CommitRetaining), @link(Rollback),
      @link(RollbackRetaining) methods, depending on @link(Action) property
      setting.
    }
    property Active : boolean read FActive write SetActive;
    { Transaction must be assigned to some database session, for which purpose
      you must use this property}
    property Database : TIBDatabase read FDatabase write FDatabase;
  end;
  
{ TIBQuery }

  PIBBookmark = ^TIBBookmark;
  TIBBookmark = record
    BookmarkData : integer;
    BookmarkFlag : TBookmarkFlag;
  end;
  
  TStatementType = (stNone, stSelect, stInsert, stUpdate, stDelete,
    stDDL, stGetSegment, stPutSegment, stExecProcedure,
    stStartTrans, stCommit, stRollback, stSelectForUpd);
  
  TIBQuery = class (TDBDataset)
  private
    FTransaction         : TIBTransaction;
    FDatabase            : TIBDatabase;
    FStatus              : array [0..19] of ISC_STATUS;
    FFieldFlag           : array [0..1023] of shortint;
    FBufferSize          : integer;
    FSQLDA               : PXSQLDA;
    FSQLDAAllocated      : integer;
    FStatement           : pointer;
    FRecordCount         : integer;
    FRecordSize          : word;
    FCurrentRecord       : integer;
    FSQL                 : TStrings;
    FPrepared            : boolean;
    FIsEOF               : boolean;
    FStatementType       : TStatementType;
    FLoadingFieldDefs    : boolean;
        
    procedure SetDatabase(Value : TIBDatabase);
    procedure SetTransaction(Value : TIBTransaction);
    procedure AllocSQLDA(Count : integer);
    procedure AllocStatement;
    procedure FreeStatement;
    procedure PrepareStatement;
    procedure DescribeStatement;
    procedure SetUpSQLVars;
    procedure AllocFldBuffers;
    procedure FreeFldBuffers;
    procedure Fetch;
    function LoadBufferFromSQLDA(Buffer : PChar): TGetResult;
    procedure GetStatementType;
    procedure SetFieldSizes;
    procedure TranslateFldType(SQLType, SQLLen : integer; var LensSet : boolean;
      var TrType : TFieldType; var TrLen : word);

    procedure ExecuteImmediate;
    procedure ExecuteParams;
    procedure Execute;
    
    // conversion methods
    procedure GetDateTime(CurrBuff, Buffer : pointer; AType : integer);
    procedure GetFloat(CurrBuff, Buffer : pointer; Field : TField);

  protected
  
    // abstract & virual methods of TDataset
    function AllocRecordBuffer: PChar; override;
    procedure FreeRecordBuffer(var Buffer: PChar); override;
    procedure GetBookmarkData(Buffer: PChar; Data: Pointer); override;
    function GetBookmarkFlag(Buffer: PChar): TBookmarkFlag; override;
    function GetFieldData(Field: TField; Buffer: Pointer): Boolean; override;
    function GetRecord(Buffer: PChar; GetMode: TGetMode; DoCheck: Boolean): TGetResult; override;
    function GetRecordCount: integer; override;
    function GetRecordSize: Word; override;
    procedure InternalAddRecord(Buffer: Pointer; AAppend: Boolean); override;
    procedure InternalClose; override;
    procedure InternalDelete; override;
    procedure InternalFirst; override;
    procedure InternalGotoBookmark(ABookmark: Pointer); override;
    procedure InternalHandleException; override;
    procedure InternalInitFieldDefs; override;
    procedure InternalInitRecord(Buffer: PChar); override;
    procedure InternalLast; override;
    procedure InternalOpen; override;
    procedure InternalPost; override;
    procedure InternalSetToRecord(Buffer: PChar); override;
    function IsCursorOpen: Boolean; override;
    procedure SetBookmarkFlag(Buffer: PChar; Value: TBookmarkFlag); override;
    procedure SetBookmarkData(Buffer: PChar; Data: Pointer); override;
    procedure SetFieldData(Field: TField; Buffer: Pointer); override;
  public
    { This method is used for executing sql statements, which
      doesn't return any rows. (insert,delete,update, and DDL commands) }
    procedure ExecSQL; virtual;
    constructor Create(AOwner : TComponent); override;
    destructor Destroy; override;
  published
    { Query must have transaction assigned. If transaction is not assigned, and database
      is, object looks, if database have default transaction, and assigns it }
    property Transaction : TIBTransaction read FTransaction write SetTransaction;
    { Use this property to determine, which database session can query use }
    property Database    : TIBDatabase read FDatabase write SetDatabase;
    { This property holds SQL command, which you want to execute }
    property SQL         : TStrings read FSQL write FSQL;
  end;
  
{ TIBStoredProc - not implemented - yet :-/}
  
  TIBStoredProc = class (TDataset)
  private
  protected
  public
  published
  end;

implementation

type

  TTm = packed record
    tm_sec : longint;
    tm_min : longint;
    tm_hour : longint;
    tm_mday : longint;
    tm_mon : longint;
    tm_year : longint;
    tm_wday : longint;
    tm_yday : longint;
    tm_isdst : longint;
    __tm_gmtoff : longint;
    __tm_zone : Pchar;
  end;

procedure CheckError(ProcName : string; Status : array of ISC_STATUS);
var
  buf : array [0..1024] of char;
  p   : pointer;
  Msg : string;
begin
  if ((Status[0] = 1) and (Status[1] <> 0)) then
  begin
    p := @Status;
    while isc_interprete(Buf, @p) > 0 do
      Msg := Msg + #10' -' + StrPas(Buf);
    raise Exception.Create(ProcName + ': ' + Msg);
  end;
end;

{ TIBDatabase }

procedure TIBDatabase.SetDBDialect;
var
  x : integer;
  Len : integer;
  Buffer : string;
  ResBuf : array [0..39] of byte;
begin
  Buffer := Chr(isc_info_db_sql_dialect) + Chr(isc_info_end);
  if isc_database_info(@FStatus, @FIBDatabaseHandle, Length(Buffer),
    @Buffer[1], SizeOf(ResBuf), @ResBuf) <> 0 then
      CheckError('TIBDatabse.SetDBDialect', FStatus);
  x := 0;
  while x < 40 do
    case ResBuf[x] of
      isc_info_db_sql_dialect : 
        begin
          Inc(x);
          Len := isc_vax_integer(@ResBuf[x], 2);
          Inc(x, 2);
          FDialect := isc_vax_integer(@ResBuf[x], Len);
          Inc(x, Len);  
        end;
      isc_info_end : Break;
    end;  
end;

procedure TIBDatabase.SetTransaction(Value : TIBTransaction);
begin
  if FTransaction = nil then
  begin
    FTransaction := Value;
    FTransaction.Database := Self;
    Exit;
  end;
  
  if (Value <> FTransaction) and (Value <> nil) then
    if (not FTransaction.Active) then
    begin
      FTransaction := Value;
      FTransaction.Database := Self;
    end
    else Exception.Create('Cannot assign transaction while old transaction active!'); 
end;

function TIBDatabase.GetHandle: pointer;
begin
  Result := FIBDatabaseHandle;
end;

procedure TIBDatabase.DoInternalConnect;
var
  DPB : string;
begin
  if Connected then
    Close;    
  DPB := chr(isc_dpb_version1);
  if (FUserName <> '') then
  begin
    DPB := DPB + chr(isc_dpb_user_name) + chr(Length(FUserName)) + FUserName;
    if (FPassword <> '') then
      DPB := DPB + chr(isc_dpb_password) + chr(Length(FPassword)) + FPassword;
  end;
  if (DatabaseName = '') then
    raise Exception.Create('TIBDatabase.Open: Database connect string not filled in!');
  FIBDatabaseHandle := nil;
  if isc_attach_database(@FStatus, Length(DatabaseName), @DatabaseName[1], @FIBDatabaseHandle, 
         Length(DPB), @DPB[1]) <> 0 then
    CheckError('TIBDatabase.Open', FStatus);
  SetDBDialect;
end;

procedure TIBDatabase.DoInternalDisconnect;
begin
  if not Connected then
  begin
    FIBDatabaseHandle := nil;
    Exit;
  end;
  isc_detach_database(@FStatus[0], @FIBDatabaseHandle);
  CheckError('TIBDatabase.Close', FStatus);
end;

procedure TIBDatabase.StartTransaction;
begin
  if FTransaction = nil then
    raise EDatabaseError.Create('TIBDatabase.StartTransaction: Transaction not set');
  FTransaction.Active := True;    
end;

procedure TIBDatabase.EndTransaction;
begin
  if FTransaction = nil then
    raise EDatabaseError.Create('TIBDatabase.EndTransaction: Transaction not set');
  FTransaction.Active := False;    
end;

constructor TIBDatabase.Create(AOwner : TComponent);
begin
  inherited Create(AOwner);
  FIBDatabaseHandle    := nil;
  FPassword            := '';
  FTransaction         := nil;
  FUserName            := '';
  FillChar(FStatus, SizeOf(FStatus), #0);
end;

destructor TIBDatabase.Destroy;
begin
  if FTransaction <> nil then
  begin
    FTransaction.Active := False;
    FTransaction.Database := nil;
  end;
  inherited Destroy;
end;

{ TIBTransaction }

procedure TIBTransaction.SetActive(Value : boolean);
begin
  if FActive = Value then Exit;
  if (FActive) and (not Value) then
    case FAction of
      caCommit            : Commit;
      caCommitRetaining   : CommitRetaining;
      caRollback          : Rollback;
      caRollbackRetaining : RollbackRetaining;
    else
      Exception.Create('TIBTransaction.SetActive: Transaction is already active.');
    end;
  if (not FActive) and (Value) then
    StartTransaction;
end;

procedure TIBTransaction.SetTPB;
begin
  FTPB := chr(isc_tpb_version3);

  case FAccessMode of
    amReadWrite : FTPB := FTPB + chr(isc_tpb_write);
    amReadOnly  : FTPB := FTPB + chr(isc_tpb_read);
  end;
  
  case FIsolationLevel of
    ilConsistent        : FTPB := FTPB + chr(isc_tpb_consistency);
    ilConcurrent        : FTPB := FTPB + chr(isc_tpb_concurrency);
    ilReadCommittedRecV : FTPB := FTPB + chr(isc_tpb_read_committed) +
      chr(isc_tpb_rec_version);
    ilReadCommitted     : FTPB := FTPB + chr(isc_tpb_read_committed) +
      chr(isc_tpb_no_rec_version);
  end;
  
  case FLockResolution of
    lrWait   : FTPB := FTPB + chr(isc_tpb_wait);
    lrNoWait : FTPB := FTPB + chr(isc_tpb_nowait);
  end;
  
  case FTableReservation of
    trSharedLockRead     : FTPB := FTPB + chr(isc_tpb_shared) + 
      chr(isc_tpb_lock_read);
    trSharedLockWrite    : FTPB := FTPB + chr(isc_tpb_shared) + 
      chr(isc_tpb_lock_write);
    trProtectedLockRead  : FTPB := FTPB + chr(isc_tpb_protected) +
      chr(isc_tpb_lock_read);
    trProtectedLockWrite : FTPB := FTPB + chr(isc_tpb_protected) +
      chr(isc_tpb_lock_write);
  end;
end;

function TIBTransaction.GetHandle: pointer;
begin
  Result := FTransactionHandle;
end;

procedure TIBTransaction.Commit;
begin
  if not FActive then Exit;
  if isc_commit_transaction(@FStatus, @FTransactionHandle) <> 0 then
    CheckError('TIBTransaction.Commit', FStatus)
  else FActive := False;
end;

procedure TIBTransaction.CommitRetaining;
begin
  if not FActive then Exit;
  if isc_commit_retaining(@FStatus, @FTransactionHandle) <> 0 then
    CheckError('TIBTransaction.CommitRetaining', FStatus);
end;

procedure TIBTransaction.Rollback;
begin
  if not FActive then Exit;
  if isc_rollback_transaction(@FStatus, @FTransactionHandle) <> 0 then
    CheckError('TIBTransaction.Rollback', FStatus)
  else FActive := False;
end;

procedure TIBTransaction.RollbackRetaining;
begin
  if not FActive then Exit;
  if isc_rollback_retaining(@FStatus, @FTransactionHandle) <> 0 then
    CheckError('TIBTransaction.RollbackRetaining', FStatus);
end;

procedure TIBTransaction.StartTransaction;
var
  DBHandle : pointer;
begin
  if Active then Active := False;
  
  if FDatabase = nil then
    Exception.Create('TIBTransaction.StartTransaction: Database not assigned!');
  
  if not Database.Connected then
    Database.Open;
  
  DBHandle := Database.GetHandle;
  SetTPB;
  FTransactionHandle := nil;
  
  if isc_start_transaction(@FStatus, @FTransactionHandle, 1,
     [@DBHandle, Length(FTPB), @FTPB[1]]) <> 0 then
    CheckError('TIBTransaction.StartTransaction',FStatus)
  else FActive := True;
end;

constructor TIBTransaction.Create(AOwner : TComponent);
begin
  inherited Create(AOwner);

  FAction := caNone;
  FActive := False;
  FAccessMode := amReadWrite;
  FIsolationLevel := ilReadCommitted;
  FLockResolution := lrWait;
  FTableReservation := trNone;
  FTransactionHandle := nil;
  FDatabase := nil;
  
  FillChar(FStatus, SizeOf(FStatus), #0);
end;

destructor TIBTransaction.Destroy;
begin
  if Database <> nil then
    Database.Transaction := nil;

{  // i really can't allow commit of transaction
  // on destroy...
}
{  
  try
    if Active then 
      Active := False;
  except
  end;
}
  
  inherited Destroy;
end;

{ TIBQuery }

procedure TIBQuery.SetTransaction(Value : TIBTransaction);
begin
  CheckInactive;
  if (FTransaction <> Value) then
    FTransaction := Value;
end;

procedure TIBQuery.SetDatabase(Value : TIBDatabase);
begin
  CheckInactive;
  if (FDatabase <> Value) then
  begin
    FDatabase := Value;
    if (FTransaction = nil) and (Assigned(FDatabase.Transaction)) then
      SetTransaction(FDatabase.Transaction);
  end;
end;

procedure TIBQuery.AllocSQLDA(Count : integer);
begin
  if FSQLDAAllocated > 0 then
    FreeMem(FSQLDA, XSQLDA_Length * FSQLDAAllocated);
  GetMem(FSQLDA, XSQLDA_Length * Count);
  FSQLDAAllocated := Count;
  FSQLDA^.Version := sqlda_version1;
  FSQLDA^.SQLN := Count; 
end;

procedure TIBQuery.AllocStatement;
var
  dh : pointer;
begin
  if not FDatabase.Connected then
    FDatabase.Open;
  dh := FDatabase.GetHandle;
  
  if isc_dsql_allocate_statement(@FStatus, @dh, @FStatement) <> 0 then
    CheckError('TIBQuery.AllocStatement', FStatus);
end;

procedure TIBQuery.FreeStatement;
begin
  if isc_dsql_free_statement(@FStatus, @FStatement, DSQL_Drop) <> 0 then
    CheckError('TIBQuery.DeallocStatement', FStatus);
  FStatement := nil;
end;

procedure TIBQuery.PrepareStatement;
var
  Buf : string;
  x   : integer;
  tr  : pointer;
begin
  tr := FTransaction.GetHandle;
  
  for x := 0 to FSQL.Count - 1 do
    Buf := Buf + FSQL[x] + ' ';
    
  if isc_dsql_prepare(@FStatus, @tr, @FStatement, 0, @Buf[1], 1, nil) <> 0 then
    CheckError('TIBQuery.PrepareStatement', FStatus);
end;

procedure TIBQuery.DescribeStatement;
begin
  if isc_dsql_describe(@FStatus, @FStatement, 1, FSQLDA) <> 0 then
    CheckError('TIBQuery.DescribeStatement', FStatus);
  if FSQLDA^.SQLD > FSQLDA^.SQLN then
  begin
    AllocSQLDA(FSQLDA^.SQLD);
    if isc_dsql_describe(@FStatus, @FStatement, 1, FSQLDA) <> 0 then
      CheckError('TIBQuery.DescribeStatement', FStatus);
  end;
end;

procedure TIBQuery.SetUpSQLVars;
var
  x : integer;
begin
  for x := 0 to FSQLDA^.SQLN - 1 do
  begin
    case FSQLDA^.SQLVar[x].SQLType of
      sql_varying + 1: 
        FSQLDA^.SQLVar[x].SQLType := sql_varying;
      sql_text + 1   : 
        FSQLDA^.SQLVar[x].SQLType := sql_text;
      sql_short, sql_short + 1, sql_long + 1:
        FSQLDA^.SQLVar[x].SQLType := sql_long;
      sql_float + 1  :
        FSQLDA^.SQLVar[x].SQLType := sql_float;
      sql_double + 1 : 
        FSQLDA^.SQLVar[x].SQLType := sql_double;
      sql_blob + 1   : 
        FSQLDA^.SQLVar[x].SQLType := sql_blob;
      sql_type_time + 1   :
        FSQLDA^.SQLVar[x].SQLType := sql_type_time;
      sql_timestamp + 1:
        FSQLDA^.SQLVar[x].SQLType := sql_timestamp;
    end; 
  end;
end;

procedure TIBQuery.AllocFldBuffers;
var
  Buf: pointer;
  x  : shortint;
begin
  {$R-}
  for x := 0 to FSQLDA^.SQLD - 1 do
  begin
    Buf := AllocMem(FSQLDA^.SQLVar[x].SQLLen);
    FSQLDA^.SQLVar[x].SQLData := Buf;
    FSQLDA^.SQLVar[x].SQLInd  := @FFieldFlag[x];
  end;
  {$R+}
end;

procedure TIBQuery.FreeFldBuffers;
var
  x  : integer;
begin
  {$R-}
  for x := 0 to FSQLDA^.SQLD - 1 do
  begin
    if FSQLDA^.SQLVar[x].SQLData <> nil then
    begin
      FreeMem(FSQLDA^.SQLVar[x].SQLData);
      FSQLDA^.SQLVar[x].SQLData := nil; 
    end;
  end;
  {$R+}
end;

procedure TIBQuery.Fetch;
var
  retcode : integer;
begin
  if not (FStatementType in [stSelect]) then 
    Exit;

  retcode := isc_dsql_fetch(@FStatus, @FStatement, 1, FSQLDA);
  if (retcode <> 0) and (retcode <> 100) then
    CheckError('TIBQuery.Fetch', FStatus);

  FIsEOF := (retcode = 100); 
end;

function TIBQuery.LoadBufferFromSQLDA(Buffer : PChar): TGetResult;
var
  x          : integer;
  VarcharLen : word;
begin
  
  Fetch;
  if FIsEOF then
  begin
    Result := grEOF;
    Exit;
  end;
  
  {$R-}
  for x := 0 to FSQLDA^.SQLD - 1 do
  begin
    with FSQLDA^.SQLVar[x] do
    begin
      if ((SQLType and not 1) = SQL_VARYING) then
      begin
        Move(SQLData^, VarcharLen, 2);
        Move((SQLData + 2)^, Buffer^, VarcharLen);
        PChar(Buffer + VarcharLen)^ := #0;
      end
      else Move(SQLData^, Buffer^, SQLLen);
      Inc(Buffer, SQLLen);
    end;
  end;
  {$R+} 
  Result := grOK;

end;

procedure TIBQuery.GetStatementType;
var
  x : integer;
  ResBuf : array [0..7] of char;
begin
  FStatementType := stNone;
  x := isc_info_sql_stmt_type;
  if isc_dsql_sql_info(@FStatus, @FStatement, SizeOf(X), 
    @x, SizeOf(ResBuf), @ResBuf) <> 0 then
    CheckError('TIBQuery.GetStatementType', FStatus);
  if Ord(ResBuf[0]) = isc_info_sql_stmt_type then
  begin
    x := isc_vax_integer(@ResBuf[1], 2);
    FStatementType := TStatementType(isc_vax_integer(@ResBuf[3], x));
  end;
end;

procedure TIBQuery.SetFieldSizes;
var
  x : integer;
begin
  FRecordSize := 0;
  FBufferSize := 0;
  {$R-}
  for x := 0 to FSQLDA^.SQLD - 1 do
    Inc(FRecordSize, FSQLDA^.SQLVar[x].SQLLen);
  {$R+}
  FBufferSize := FRecordSize + SizeOf(TIBBookmark);
end;

procedure TIBQuery.TranslateFldType(SQLType, SQLLen : integer; var LensSet : boolean;
  var TrType : TFieldType; var TrLen : word);
begin
  LensSet := False;

  case (SQLType and not 1) of
    SQL_VARYING : 
      begin
        LensSet := True;
        TrType := ftString;
        TrLen := SQLLen;
      end;
    SQL_TEXT :      
      begin
        LensSet := True;
        TrType := ftString;
        TrLen := SQLLen;
      end;
    SQL_TYPE_DATE :
        TrType := ftDateTime;
    SQL_TYPE_TIME :
        TrType := ftDateTime;
    SQL_TIMESTAMP :
        TrType := ftDateTime;
    SQL_ARRAY :
      begin
      end;
    SQL_BLOB : 
      begin
      end;
    SQL_SHORT :
      begin
        LensSet := True;
        TrLen := SQLLen;
        TrType := ftInteger;
      end;
    SQL_LONG :
      begin
        LensSet := True;
        TrLen := SQLLen;
        TrType := ftInteger;
      end;
    SQL_INT64 :
        {TrType := ftInt64};
    SQL_DOUBLE :
      begin
        LensSet := True;
        TrLen := SQLLen;
        TrType := ftFloat;
      end;
    SQL_FLOAT :
      begin
        LensSet := True;
        TrLen := SQLLen;
        TrType := ftFloat;
      end;
  end;
end;

procedure TIBQuery.ExecuteImmediate;
begin
end;

procedure TIBQuery.ExecuteParams;
begin
  //!! to be implemented
end;

procedure TIBQuery.Execute;
var
  tr : pointer;
begin
  tr := FTransaction.GetHandle;
  if isc_dsql_execute(@FStatus, @tr, @FStatement, 1, nil) <> 0 then
    CheckError('TIBQuery.Execute', FStatus);
end;

procedure TIBQuery.GetDateTime(CurrBuff, Buffer : pointer; AType : integer);
var
  CTime : TTm;          // C struct time
  STime : TSystemTime;  // System time
  PTime : TDateTime;    // Pascal time
begin
  case (AType and not 1) of 
    SQL_TYPE_DATE : 
      isc_decode_sql_date(PISC_DATE(CurrBuff), @CTime);
    SQL_TYPE_TIME :
      isc_decode_sql_time(PISC_TIME(CurrBuff), @CTime);
    SQL_TIMESTAMP :
      isc_decode_timestamp(PISC_TIMESTAMP(CurrBuff), @CTime);
  end;
  STime.Year        := CTime.tm_year + 1900;
  STime.Month       := CTime.tm_mon + 1;
  STime.Day         := CTime.tm_mday;
  STime.Hour        := CTime.tm_hour;
  STime.Minute      := CTime.tm_min;
  STime.Second      := CTime.tm_sec;
  STime.Millisecond := 0;
  
  PTime := SystemTimeToDateTime(STime);
  Move(PTime, Buffer^, SizeOf(PTime));
end;

procedure TIBQuery.GetFloat(CurrBuff, Buffer : pointer; Field : TField);
var
  Ext : extended;
  Dbl : double;
  Sin : single;
begin
  case Field.Size of
    4 :
      begin
        Move(CurrBuff^, Sin, 4);
        Ext := Sin;
      end;
    8 :
      begin
        Move(CurrBuff^, Dbl, 8);
        Ext := Dbl;
      end;
    10: Move(CurrBuff^, Ext, 10);
  end;
  Move(Ext, Buffer^, 10);
end;

function TIBQuery.AllocRecordBuffer: PChar;
begin
  Result := AllocMem(FBufferSize);
end;

procedure TIBQuery.FreeRecordBuffer(var Buffer: PChar);
begin
  FreeMem(Buffer);
end;

procedure TIBQuery.GetBookmarkData(Buffer: PChar; Data: Pointer);
begin
  PInteger(Data)^ := PIBBookmark(Buffer + FRecordSize)^.BookmarkData; 
end;

function TIBQuery.GetBookmarkFlag(Buffer: PChar): TBookmarkFlag;
begin
  Result := PIBBookmark(Buffer + FRecordSize)^.BookmarkFlag;
end;

function TIBQuery.GetFieldData(Field: TField; Buffer: Pointer): Boolean;
var
  x : longint;
  b : longint;
  CurrBuff : PChar;
begin
  Result := False;
  CurrBuff := ActiveBuffer;
  
  for x := 0 to FSQLDA^.SQLD - 1 do
  begin
    {$R-}
    if (Field.FieldName = FSQLDA^.SQLVar[x].SQLName) then
    begin
      case Field.DataType of
        ftInteger : 
          begin
            b := 0;
            Move(b, Buffer^, 4);
            Move(CurrBuff^, Buffer^, Field.Size);
          end;
        ftDate, ftTime, ftDateTime:
          GetDateTime(CurrBuff, Buffer, FSQLDA^.SQLVar[x].SQLType);
        ftString  :
          begin
            Move(CurrBuff^, Buffer^, Field.Size);
            PChar(Buffer + Field.Size)^ := #0;
          end;
        ftFloat   : 
          GetFloat(CurrBuff, Buffer, Field);
      end;
      
      Result := True;
      
      Break;
    end
    else Inc(CurrBuff, FSQLDA^.SQLVar[x].SQLLen);
    {$R+}
  end;
end;

function TIBQuery.GetRecord(Buffer: PChar; GetMode: TGetMode; DoCheck: Boolean): TGetResult;
begin
  if FStatementType <> stSelect then 
  begin
    Result := grEOF;
    Exit;
  end;
  if FIsEOF then
    Result := grEOF
  else begin
    Result := grOK;
    case GetMode of
      gmPrior :
        if FCurrentRecord <= 0 then
        begin
          Result := grBOF;
          FCurrentRecord := -1;
        end
        else Dec(FCurrentRecord);
      gmCurrent : 
        if (FCurrentRecord < 0) or (FCurrentRecord >= RecordCount) then
          Result := grError;
      gmNext : 
        if FCurrentRecord >= (RecordCount - 1) then
        begin
          Result := LoadBufferFromSQLDA(Buffer);
          if Result = grOK then
          begin
            Inc(FCurrentRecord);
            Inc(FRecordCount);
          end;
        end
        else Inc(FCurrentRecord);
    end;
  end;
  
  if Result = grOK then
  begin
    with PIBBookmark(Buffer + FRecordSize)^ do
    begin
      BookmarkData := FCurrentRecord;
      BookmarkFlag := bfCurrent;
    end;
  end
  else if (Result = grError) then
    DatabaseError('No record');
end;

function TIBQuery.GetRecordCount: integer;
begin
  Result := FRecordCount;
end;

function TIBQuery.GetRecordSize: Word;
begin
  Result := FRecordSize;
end;

procedure TIBQuery.InternalAddRecord(Buffer: Pointer; AAppend: Boolean);
begin
  // not implemented - sql dataset
end;

procedure TIBQuery.InternalClose;
begin
  FreeFldBuffers;
  FreeStatement;
  if DefaultFields then
    DestroyFields;
  FIsEOF := False;
  FCurrentRecord := -1;
  FBufferSize := 0;
  FRecordSize := 0;
  FRecordCount:= 0;
end;

procedure TIBQuery.InternalDelete;
begin
  // not implemented - sql dataset
end;

procedure TIBQuery.InternalFirst;
begin
  FCurrentRecord := -1;
end;

procedure TIBQuery.InternalGotoBookmark(ABookmark: Pointer);
begin
  FCurrentRecord := PInteger(ABookmark)^;
end;

procedure TIBQuery.InternalHandleException;
begin
end;

procedure TIBQuery.InternalInitFieldDefs;
var
  x         : integer;
  lenset    : boolean;
  TransLen  : word;
  TransType : TFieldType;
begin
  if FLoadingFieldDefs then
    Exit;

  FLoadingFieldDefs := True;
  
  try
    FieldDefs.Clear;
    {$R-}
    for x := 0 to FSQLDA^.SQLD - 1 do
    begin
      TranslateFldType(FSQLDA^.SQLVar[x].SQLType, FSQLDA^.SQLVar[x].SQLLen, lenset, 
        TransType, TransLen);
      TFieldDef.Create(FieldDefs, FSQLDA^.SQLVar[x].SQLName, TransType, 
        TransLen, False, (x + 1));
    end;
    {$R+}
  finally
    FLoadingFieldDefs := False;
  end;
end;

procedure TIBQuery.InternalInitRecord(Buffer: PChar);
begin
  FillChar(Buffer^, FBufferSize, #0);
end;

procedure TIBQuery.InternalLast;
begin
  FCurrentRecord := RecordCount;
end;

procedure TIBQuery.InternalOpen;
begin
  try
    AllocStatement;
    PrepareStatement;
    GetStatementType;
    if FStatementType in [stSelect] then
    begin
      DescribeStatement;
      AllocFldBuffers;
      Execute;
      InternalInitFieldDefs;
      if DefaultFields then
        CreateFields;
      SetFieldSizes;
      BindFields(True);
    end
    else Execute;
  except
    on E:Exception do
      raise;
  end;
end;

procedure TIBQuery.InternalPost;
begin
  // not implemented - sql dataset
end;

procedure TIBQuery.InternalSetToRecord(Buffer: PChar);
begin
  FCurrentRecord := PIBBookmark(Buffer + FRecordSize)^.BookmarkData;
end;

function TIBQuery.IsCursorOpen: Boolean;
begin
  Result := False;
end;

procedure TIBQuery.SetBookmarkFlag(Buffer: PChar; Value: TBookmarkFlag);
begin
  PIBBookmark(Buffer + FRecordSize)^.BookmarkFlag := Value;
end;

procedure TIBQuery.SetBookmarkData(Buffer: PChar; Data: Pointer);
begin
  PIBBookmark(Buffer + FRecordSize)^.BookmarkData := PInteger(Data)^;
end;

procedure TIBQuery.SetFieldData(Field: TField; Buffer: Pointer);
begin
end;

// public part

procedure TIBQuery.ExecSQL;
begin
  AllocStatement;
  PrepareStatement;
  GetStatementType;
  Execute;
  FreeStatement;
end;

constructor TIBQuery.Create(AOwner : TComponent);
begin
  inherited Create(AOwner);
  FillChar(FFieldFlag, SizeOf(FFieldFlag), #0);
  FSQL := TStringList.Create;
  FStatement := nil;
  FCurrentRecord := -1;
  FDatabase := nil;
  FTransaction := nil;
  FSQLDAAllocated := 0;
  FLoadingFieldDefs := False;
  FPrepared := False;
  AllocSQLDA(10);
end;

destructor TIBQuery.Destroy;
begin
  if Active then Close;
  FSQL.Free;
  inherited Destroy;
  FreeMem(FSQLDA, XSQLDA_Length * FSQLDAAllocated);
end;

{ TIBStoredProc }

end.

