unit IBConnection; {$mode objfpc}{$H+} {$Define LinkDynamically} interface uses Classes, SysUtils, sqldb, db, dbconst, bufdataset, {$IfDef LinkDynamically} ibase60dyn; {$Else} ibase60; {$EndIf} const DEFDIALECT = 3; MAXBLOBSEGMENTSIZE = 65535; //Maximum number of bytes that fit in a blob segment. type TDatabaseInfo = record Dialect : integer; //Dialect set in database ODSMajorVersion : integer; //On-Disk Structure version of file ServerVersion : string; //Representation of major.minor (.build) ServerVersionString : string; //Complete version string, including name, platform end; TStatusVector = array [0..19] of ISC_STATUS; { EIBDatabaseError } EIBDatabaseError = class(ESQLDatabaseError) private FStatusVector: TStatusVector; public Property StatusVector: TStatusVector Read FStatusVector Write FStatusVector; property GDSErrorCode: integer read ErrorCode; deprecated 'Please use ErrorCode instead of GDSErrorCode'; // Nov 2014 end; { TIBCursor } TIBCursor = Class(TSQLCursor) protected Status : TStatusVector; TransactionHandle : pointer; StatementHandle : pointer; SQLDA : PXSQLDA; in_SQLDA : PXSQLDA; ParamBinding : array of integer; FieldBinding : array of integer; end; TIBTrans = Class(TSQLHandle) protected TransactionHandle : pointer; TPB : string; // Transaction parameter buffer Status : TStatusVector; end; { TIBConnection } TIBConnection = class (TSQLConnection) private FCheckTransactionParams: Boolean; FDatabaseHandle : pointer; FStatus : TStatusVector; FDatabaseInfo : TDatabaseInfo; FDialect : integer; FBlobSegmentSize : word; //required for backward compatibilty; not used FUseConnectionCharSetIfNone: Boolean; FWireCompression : Boolean; procedure ConnectFB; procedure AllocSQLDA(var aSQLDA : PXSQLDA;Count : integer); // Metadata: procedure GetDatabaseInfo; //Queries for various information from server once connected function InterpretTransactionParam(S: String; var TPB: AnsiChar; out AValue: String): Boolean; procedure ResetDatabaseInfo; //Useful when disconnecting function GetDialect: integer; function GetODSMajorVersion: integer; function ParseServerVersion(const CompleteVersion: string): string; //Extract version info from complete version identification string // conversion methods procedure TranslateFldType(SQLType, SQLSubType, SQLLen, SQLScale : integer; out TrType : TFieldType; out TrLen, TrPrec : word); procedure GetDateTime(CurrBuff, Buffer : pointer; AType : integer); procedure SetDateTime(CurrBuff: pointer; PTime : TDateTime; AType : integer); procedure GetFloat(CurrBuff, Buffer : pointer; Size : Byte); procedure SetFloat(CurrBuff: pointer; Dbl: Double; Size: integer); procedure CheckError(ProcName : string; Status : PISC_STATUS); procedure SetParameters(cursor : TSQLCursor; aTransation : TSQLTransaction; AParams : TParams); procedure FreeSQLDABuffer(var aSQLDA : PXSQLDA); function IsDialectStored: boolean; protected procedure DoConnect; override; procedure DoInternalConnect; override; procedure DoInternalDisconnect; override; function GetHandle : pointer; override; Function AllocateCursorHandle : TSQLCursor; override; Procedure DeAllocateCursorHandle(var cursor : TSQLCursor); override; Function AllocateTransactionHandle : TSQLHandle; override; procedure PrepareStatement(cursor: TSQLCursor;ATransaction : TSQLTransaction;buf : string; AParams : TParams); override; procedure UnPrepareStatement(cursor : TSQLCursor); override; procedure FreeFldBuffers(cursor : TSQLCursor); override; procedure Execute(cursor: TSQLCursor;atransaction:tSQLtransaction; AParams : TParams); override; procedure AddFieldDefs(cursor: TSQLCursor;FieldDefs : TFieldDefs); override; function Fetch(cursor : TSQLCursor) : boolean; override; function LoadField(cursor : TSQLCursor;FieldDef : TfieldDef;buffer : pointer; out CreateBlob : boolean) : boolean; override; function GetTransactionHandle(trans : TSQLHandle): pointer; override; function Commit(trans : TSQLHandle) : boolean; override; function RollBack(trans : TSQLHandle) : boolean; override; function StartDBTransaction(trans : TSQLHandle; AParams : string) : boolean; override; procedure CommitRetaining(trans : TSQLHandle); override; procedure RollBackRetaining(trans : TSQLHandle); override; procedure UpdateIndexDefs(IndexDefs : TIndexDefs;TableName : string); override; procedure LoadBlobIntoBuffer(FieldDef: TFieldDef;ABlobBuf: PBufBlobField; cursor: TSQLCursor; ATransaction : TSQLTransaction); override; function RowsAffected(cursor: TSQLCursor): TRowsCount; override; function GetSchemaInfoSQL(SchemaType : TSchemaType; SchemaObjectName, SchemaPattern : string) : string; override; function GetNextValueSQL(const SequenceName: string; IncrementBy: Integer): string; override; public constructor Create(AOwner : TComponent); override; function GetConnectionInfo(InfoType:TConnInfoType): string; override; procedure CreateDB; override; procedure DropDB; override; // Segment size is not used in the code; property kept for backward compatibility property BlobSegmentSize : word read FBlobSegmentSize write FBlobSegmentSize; deprecated; property ODSMajorVersion : integer read GetODSMajorVersion; //ODS major version number; influences database compatibility/feature level. published property DatabaseName; property Dialect : integer read GetDialect write FDialect stored IsDialectStored default DEFDIALECT; // Set this to true to have StartTransaction check transaction parameters. If False, unknown parameters are ignored. Property CheckTransactionParams : Boolean Read FCheckTransactionParams write FCheckTransactionParams; property KeepConnection; property LoginPrompt; property Params; property OnLogin; Property Port stored false; Property UseConnectionCharSetIfNone : Boolean Read FUseConnectionCharSetIfNone Write FUseConnectionCharSetIfNone; property WireCompression: Boolean read FWireCompression write FWireCompression default False; end; { TIBConnectionDef } TIBConnectionDef = Class(TConnectionDef) Class Function TypeName : String; override; Class Function ConnectionClass : TSQLConnectionClass; override; Class Function Description : String; override; Class Function DefaultLibraryName : String; override; Class Function LoadFunction : TLibraryLoadFunction; override; Class Function UnLoadFunction : TLibraryUnLoadFunction; override; Class Function LoadedLibraryName: string; override; end; implementation uses StrUtils, FmtBCD; const SQL_BOOLEAN_INTERBASE = 590; SQL_BOOLEAN_FIREBIRD = 32764; SQL_NULL = 32767; INVALID_DATA = -1; procedure TIBConnection.CheckError(ProcName : string; Status : PISC_STATUS); var i,ErrorCode : longint; Msg, SQLState : string; Buf : array [0..1023] of char; aStatusVector: TStatusVector; Exc : EIBDatabaseError; begin if ((Status[0] = 1) and (Status[1] <> 0)) then begin ErrorCode := Status[1]; {$IFDEF LinkDynamically} if assigned(fb_sqlstate) then // >= Firebird 2.5 begin fb_sqlstate(Buf, Status); SQLState := StrPas(Buf); end; {$ENDIF} { get a local copy of status vector } for i := 0 to 19 do aStatusVector[i] := Status[i]; Msg := ''; while isc_interprete(Buf, @Status) > 0 do Msg := Msg + LineEnding + ' -' + StrPas(Buf); Exc:=EIBDatabaseError.CreateFmt('%s : %s', [ProcName,Msg], Self, ErrorCode, SQLState); Exc.StatusVector:=aStatusVector; raise Exc; end; end; constructor TIBConnection.Create(AOwner : TComponent); begin inherited; FConnOptions := FConnOptions + [sqSupportParams, sqEscapeRepeat, sqSupportReturning, sqSequences]; FBlobSegmentSize := 65535; //Shows we're using the maximum segment size FDialect := INVALID_DATA; FWireCompression := False; ResetDatabaseInfo; end; function TIBConnection.GetTransactionHandle(trans : TSQLHandle): pointer; begin if Assigned(trans) then Result := (trans as TIBTrans).TransactionHandle else Result := nil; end; function TIBConnection.Commit(trans : TSQLHandle) : boolean; begin result := false; with (trans as TIBTrans) do if isc_commit_transaction(@Status[0], @TransactionHandle) <> 0 then CheckError('Commit', Status) else result := true; end; function TIBConnection.RollBack(trans : TSQLHandle) : boolean; begin result := false; if isc_rollback_transaction(@TIBTrans(trans).Status[0], @TIBTrans(trans).TransactionHandle) <> 0 then CheckError('Rollback', TIBTrans(trans).Status) else result := true; end; function TIBConnection.InterpretTransactionParam(S: String; var TPB: AnsiChar; out AValue: String): Boolean; Const Prefix = 'isc_tpb_'; PrefixLen = Length(Prefix); maxParam = 21; TPBNames : Array[1..maxParam] Of String = // 5 on a line. Lowercase ('consistency','concurrency','shared','protected','exclusive', 'wait','nowait','read','write','lock_read', 'lock_write','verb_time','commit_time','ignore_limbo','read_committed', 'autocommit','rec_version','no_rec_version','restart_requests','no_auto_undo', 'lock_timeout'); Var P : Integer; begin TPB:=#0; Result:=False; P:=Pos('=',S); If P<>0 then begin AValue:=Copy(S,P+1,Length(S)-P); S:=Copy(S,1,P-1); end; S:=LowerCase(S); P:=Pos(Prefix,S); if P<>0 then Delete(S,1,P+PrefixLen-1); Result:=(Copy(S,1,7)='version') and (Length(S)=8); if Result then TPB:=S[8] else begin P:=MaxParam; While (P>0) and (S<>TPBNames[P]) do Dec(P); Result:=P>0; if Result then TPB:=Char(P); end; end; function TIBConnection.StartDBTransaction(trans: TSQLHandle; AParams: string ): boolean; Var DBHandle:pointer; I : integer; S :string; tpbv,version : ansichar; prVal :String; pInt :^Int32; LTPB : String; // Local TPB IBTrans : TIBTrans; Begin Result:=False; DBHandle:=GetHandle; Version:=#0; I:=1; IBTrans:=(Trans as TIBTrans); LTPB:=''; S:=ExtractSubStr(AParams,I,stdWordDelims); While (S<>'') do begin If Not InterpretTransactionParam(S,tpbv,prVal) then begin If CheckTransactionParams then DatabaseError('Invalid parameter for transaction: "'+S+'"',Self); end else begin // Check Version if (tpbv>='1') then begin Version:=tpbv; // Check value if Not (Version in ['1','3']) then DatabaseError('Invalid version specified for transaction: "'+Version+'"',Self); end else begin LTPB:=LTPB+tpbv; Case Ord(tpbv) Of isc_tpb_lock_read, isc_tpb_lock_write: Begin If prVal='' Then DatabaseErrorFmt('Table name must be specified for "%s"',[S],Self); LTPB:=LTPB+Char(Length(prVal))+prVal; End; isc_tpb_lock_timeout: Begin //In case of using lock timeout we need add timeout If prVal='' Then DatabaseErrorFmt('Timeout must be specified for "%s"',[S],Self); LTPB:=LTPB+Char(SizeOf(ISC_LONG)); SetLength(LTPB,Length(LTPB)+SizeOf(ISC_LONG)); pInt:=@LTPB[Length(LTPB)-SizeOf(ISC_LONG)+1]; pInt^:=StrToInt(prVal); End; End; end; end; S:=ExtractSubStr(AParams,I,stdWordDelims); end; // Default version. If Version=#0 then Version:='3'; // Construct block. With IBTrans do begin TPB:=Char(Ord(Version)-Ord('0'))+LTPB; TransactionHandle:=Nil; If isc_start_transaction(@Status[0],@TransactionHandle,1,[@DBHandle,Length(TPB),@TPB[1]])<>0 Then CheckError('StartTransaction',Status) Else Result := True End End; procedure TIBConnection.CommitRetaining(trans : TSQLHandle); begin with trans as TIBtrans do if isc_commit_retaining(@Status[0], @TransactionHandle) <> 0 then CheckError('CommitRetaining', Status); end; procedure TIBConnection.RollBackRetaining(trans : TSQLHandle); begin with trans as TIBtrans do if isc_rollback_retaining(@Status[0], @TransactionHandle) <> 0 then CheckError('RollBackRetaining', Status); end; procedure TIBConnection.DropDB; begin CheckDisConnected; {$IfDef LinkDynamically} InitialiseIBase60; {$EndIf} ConnectFB; if isc_drop_database(@FStatus[0], @FDatabaseHandle) <> 0 then CheckError('DropDB', FStatus); {$IfDef LinkDynamically} ReleaseIBase60; {$EndIf} end; procedure TIBConnection.CreateDB; var ASQLDatabaseHandle, ASQLTransactionHandle : pointer; CreateSQL : String; pagesize : String; begin CheckDisConnected; {$IfDef LinkDynamically} InitialiseIBase60; {$EndIf} ASQLDatabaseHandle := nil; ASQLTransactionHandle := nil; CreateSQL := 'CREATE DATABASE '; if HostName <> '' then CreateSQL := CreateSQL + ''''+ HostName+':'+DatabaseName + '''' else CreateSQL := CreateSQL + '''' + DatabaseName + ''''; if UserName <> '' then CreateSQL := CreateSQL + ' USER ''' + Username + ''''; if Password <> '' then CreateSQL := CreateSQL + ' PASSWORD ''' + Password + ''''; pagesize := Params.Values['PAGE_SIZE']; if pagesize <> '' then CreateSQL := CreateSQL + ' PAGE_SIZE '+pagesize; if CharSet <> '' then CreateSQL := CreateSQL + ' DEFAULT CHARACTER SET ' + CharSet; if isc_dsql_execute_immediate(@FStatus[0],@ASQLDatabaseHandle,@ASQLTransactionHandle,length(CreateSQL),@CreateSQL[1],Dialect,nil) <> 0 then CheckError('CreateDB', FStatus); if isc_detach_database(@FStatus[0], @ASQLDatabaseHandle) <> 0 then CheckError('CreateDB', FStatus); {$IfDef LinkDynamically} ReleaseIBase60; {$EndIf} end; procedure TIBConnection.DoInternalConnect; begin {$IfDef LinkDynamically} InitialiseIBase60; {$EndIf} inherited dointernalconnect; ConnectFB; end; procedure TIBConnection.DoInternalDisconnect; begin Inherited; FDialect := INVALID_DATA; if not Connected then begin ResetDatabaseInfo; FDatabaseHandle := nil; Exit; end; if isc_detach_database(@FStatus[0], @FDatabaseHandle) <> 0 then CheckError('Close', FStatus); {$IfDef LinkDynamically} ReleaseIBase60; {$ELSE} // Shutdown embedded subsystem with timeout 300ms (Firebird 2.5+) // Required before unloading library; has no effect on non-embedded client if (pointer(fb_shutdown)<>nil) and (fb_shutdown(300,1)<>0) then begin //todo: log error; still try to unload library below as the timeout may have been insufficient end; {$EndIf} end; function TIBConnection.GetConnectionInfo(InfoType: TConnInfoType): string; begin result:=''; {$IFDEF LinkDynamically} InitialiseIBase60; {$ENDIF} try case InfoType of citServerType: // Firebird returns own name in ServerVersion; Interbase 7.5 doesn't. if Pos('Firebird', FDatabaseInfo.ServerVersionString)=0 then result := 'Interbase' else result := 'Firebird'; citServerVersion: // Firebird returns major.minor, Interbase major.minor.build result := FDatabaseInfo.ServerVersion; citServerVersionString: result := FDatabaseInfo.ServerVersionString; citClientName: result:=TIBConnectionDef.LoadedLibraryName; else //including citClientVersion, for which no single IB+FB and Win+*nux solution exists result:=inherited GetConnectionInfo(InfoType); end; finally {$IFDEF LinkDynamically} ReleaseIBase60; {$ENDIF} end; end; procedure TIBConnection.GetDatabaseInfo; // Asks server for multiple values const ResBufHigh = 512; //hopefully enough to include version string as well. var x : integer; Len : integer; ReqBuf : array [0..3] of byte; ResBuf : array [0..ResBufHigh] of byte; // should be big enough for version string etc begin ResetDatabaseInfo; if Connected then begin ReqBuf[0] := isc_info_ods_version; ReqBuf[1] := isc_info_version; ReqBuf[2] := isc_info_db_sql_dialect; ReqBuf[3] := isc_info_end; if isc_database_info(@FStatus[0], @FDatabaseHandle, Length(ReqBuf), pchar(@ReqBuf[0]), SizeOf(ResBuf), pchar(@ResBuf[0])) <> 0 then CheckError('CacheServerInfo', FStatus); x := 0; while x < ResBufHigh+1 do case ResBuf[x] of isc_info_db_sql_dialect : begin Inc(x); Len := isc_vax_integer(pchar(@ResBuf[x]), 2); Inc(x, 2); FDatabaseInfo.Dialect := isc_vax_integer(pchar(@ResBuf[x]), Len); Inc(x, Len); end; isc_info_ods_version : begin Inc(x); Len := isc_vax_integer(pchar(@ResBuf[x]), 2); Inc(x, 2); FDatabaseInfo.ODSMajorVersion := isc_vax_integer(pchar(@ResBuf[x]), Len); Inc(x, Len); end; isc_info_version : begin Inc(x); Len := isc_vax_integer(pchar(@ResBuf[x]), 2); Inc(x, 2); SetString(FDatabaseInfo.ServerVersionString, PAnsiChar(@ResBuf[x + 2]), Len-2); FDatabaseInfo.ServerVersion := ParseServerVersion(FDatabaseInfo.ServerVersionString); Inc(x, Len); end; isc_info_end, isc_info_error : Break; isc_info_truncated : Break; //result buffer too small; fix your code! else inc(x); end; end; end; procedure TIBConnection.ResetDatabaseInfo; begin FDatabaseInfo.Dialect:=0; FDatabaseInfo.ODSMajorVersion:=0; FDatabaseInfo.ServerVersion:=''; FDatabaseInfo.ServerVersionString:=''; // don't confuse applications with 'Firebird' or 'Interbase' end; function TIBConnection.GetODSMajorVersion: integer; begin result:=FDatabaseInfo.ODSMajorVersion; end; function TIBConnection.ParseServerVersion(const CompleteVersion: string): string; // String representation of integer version number derived from // major.minor.build => should give e.g. 020501 const Delimiter = '.'; DigitsPerNumber = 2; MaxNumbers = 3; var BeginPos,EndPos,StartLook,i: integer; NumericPart: string; begin result := ''; // Ignore 6.x version number in front of "Firebird" StartLook := Pos('Firebird', CompleteVersion); if StartLook = 0 then StartLook := 1; BeginPos := 0; // Catch all numerics + decimal point: for i := StartLook to Length(CompleteVersion) do begin if (BeginPos > 0) and ((CompleteVersion[i] < '0') or (CompleteVersion[i] > '9')) and (CompleteVersion[i] <> '.') then begin EndPos := i - 1; break; end; if (BeginPos = 0) and (CompleteVersion[i] >= '0') and (CompleteVersion[i] <= '9') then begin BeginPos := i; end; end; if BeginPos > 0 then begin NumericPart := copy(CompleteVersion, BeginPos, 1+EndPos-BeginPos); BeginPos := 1; for i := 1 to MaxNumbers do begin EndPos := PosEx(Delimiter,NumericPart,BeginPos); if EndPos > 0 then begin result := result + rightstr(StringOfChar('0',DigitsPerNumber)+copy(NumericPart,BeginPos,EndPos-BeginPos),DigitsPerNumber); BeginPos := EndPos+1; end else begin result := result + rightstr(StringOfChar('0',DigitsPerNumber)+copy(NumericPart,BeginPos,Length(NumericPart)),DigitsPerNumber); break; end; end; result := leftstr(result + StringOfChar('0',DigitsPerNumber * MaxNumbers), DigitsPerNumber * MaxNumbers); end; end; procedure TIBConnection.ConnectFB; const isc_dpb_config = 87; CStr_WireCompression = 'WireCompression=true'; var ADatabaseName: String; DPB: string; HN : String; begin DPB := chr(isc_dpb_version1); if (UserName <> '') then begin DPB := DPB + chr(isc_dpb_user_name) + chr(Length(UserName)) + UserName; if (Password <> '') then DPB := DPB + chr(isc_dpb_password) + chr(Length(Password)) + Password; end; if (Role <> '') then DPB := DPB + chr(isc_dpb_sql_role_name) + chr(Length(Role)) + Role; if Length(CharSet) > 0 then DPB := DPB + Chr(isc_dpb_lc_ctype) + Chr(Length(CharSet)) + CharSet; if WireCompression or (SameText(Params.values['WireCompression'],'true')) then DPB := DPB + Chr(isc_dpb_config) + Chr(Length(CStr_WireCompression)) + CStr_WireCompression; FDatabaseHandle := nil; HN:=HostName; if HN <> '' then begin if Port<>0 then HN:=HN+'/'+IntToStr(Port); ADatabaseName := HN+':'+DatabaseName end else ADatabaseName := DatabaseName; if isc_attach_database(@FStatus[0], Length(ADatabaseName), @ADatabaseName[1], @FDatabaseHandle, Length(DPB), @DPB[1]) <> 0 then CheckError('DoInternalConnect', FStatus); end; function TIBConnection.GetDialect: integer; begin if FDialect = INVALID_DATA then begin if FDatabaseInfo.Dialect=0 then Result := DEFDIALECT else Result := FDatabaseInfo.Dialect; end else Result := FDialect; end; procedure TIBConnection.AllocSQLDA(var aSQLDA : PXSQLDA;Count : integer); begin FreeSQLDABuffer(aSQLDA); if count > -1 then begin reAllocMem(aSQLDA, XSQLDA_Length(Count)); { Zero out the memory block to avoid problems with exceptions within the constructor of this class. } FillChar(aSQLDA^, XSQLDA_Length(Count), 0); aSQLDA^.Version := sqlda_version1; aSQLDA^.SQLN := Count; end else reAllocMem(aSQLDA,0); end; procedure TIBConnection.TranslateFldType(SQLType, SQLSubType, SQLLen, SQLScale : integer; out TrType : TFieldType; out TrLen, TrPrec : word); begin TrLen := 0; TrPrec := 0; if SQLScale < 0 then begin TrLen := abs(SQLScale); if (TrLen <= MaxBCDScale) then //Note: NUMERIC(18,3) or (17,2) must be mapped to ftFmtBCD, but we do not know Precision TrType := ftBCD else TrType := ftFMTBcd; case (SQLType and not 1) of SQL_SHORT : TrPrec := 4; SQL_LONG : TrPrec := 9; SQL_DOUBLE, SQL_INT64 : TrPrec := 18; else TrPrec := SQLLen; end; end else case (SQLType and not 1) of SQL_VARYING : begin TrType := ftString; TrLen := SQLLen; end; SQL_TEXT : begin TrType := ftFixedChar; TrLen := SQLLen; end; SQL_TYPE_DATE : TrType := ftDate; SQL_TYPE_TIME : TrType := ftTime; SQL_TIMESTAMP : TrType := ftDateTime; SQL_ARRAY : begin TrType := ftArray; TrLen := SQLLen; end; SQL_BLOB : begin if SQLSubType = isc_blob_text then TrType := ftMemo else TrType := ftBlob; TrLen := SQLLen; end; SQL_SHORT : TrType := ftSmallint; SQL_LONG : TrType := ftInteger; SQL_INT64 : TrType := ftLargeInt; SQL_DOUBLE : TrType := ftFloat; SQL_FLOAT : TrType := ftFloat; SQL_BOOLEAN_INTERBASE, SQL_BOOLEAN_FIREBIRD : TrType := ftBoolean; else TrType := ftUnknown; end; end; function TIBConnection.AllocateCursorHandle: TSQLCursor; var curs : TIBCursor; begin curs := TIBCursor.create; curs.sqlda := nil; curs.StatementHandle := nil; curs.FPrepared := False; AllocSQLDA(curs.SQLDA,0); result := curs; end; procedure TIBConnection.DeAllocateCursorHandle(var cursor : TSQLCursor); begin if assigned(cursor) then with cursor as TIBCursor do begin AllocSQLDA(SQLDA,-1); AllocSQLDA(in_SQLDA,-1); end; FreeAndNil(cursor); end; function TIBConnection.AllocateTransactionHandle: TSQLHandle; begin result := TIBTrans.create; end; procedure TIBConnection.PrepareStatement(cursor: TSQLCursor;ATransaction : TSQLTransaction;buf : string; AParams : TParams); var DatabaseHandle : pointer; x : Smallint; info_request : string; resbuf : array[0..7] of byte; blockSize : integer; IBStatementType: integer; begin with cursor as TIBcursor do begin DatabaseHandle := GetHandle; TransactionHandle := aTransaction.Handle; if isc_dsql_allocate_statement(@Status[0], @DatabaseHandle, @StatementHandle) <> 0 then CheckError('PrepareStatement', Status); if assigned(AParams) and (AParams.count > 0) then begin buf := AParams.ParseSQL(buf,false,sqEscapeSlash in ConnOptions, sqEscapeRepeat in ConnOptions,psInterbase,paramBinding); if LogEvent(detActualSQL) then Log(detActualSQL,Buf); end; if isc_dsql_prepare(@Status[0], @TransactionHandle, @StatementHandle, 0, @Buf[1], Dialect, nil) <> 0 then CheckError('PrepareStatement', Status); if assigned(AParams) and (AParams.count > 0) then begin AllocSQLDA(in_SQLDA,Length(ParamBinding)); if isc_dsql_describe_bind(@Status[0], @StatementHandle, 1, in_SQLDA) <> 0 then CheckError('PrepareStatement', Status); if in_SQLDA^.SQLD > in_SQLDA^.SQLN then DatabaseError(SParameterCountIncorrect,self); {$push} {$R-} for x := 0 to in_SQLDA^.SQLD - 1 do with in_SQLDA^.SQLVar[x] do begin if ((SQLType and not 1) = SQL_VARYING) then SQLData := AllocMem(in_SQLDA^.SQLVar[x].SQLLen+2) else if SQLType <> SQL_NULL then SQLData := AllocMem(in_SQLDA^.SQLVar[x].SQLLen); // Always force the creation of slqind for parameters. It could be // that a database trigger takes care of inserting null values, so // it should always be possible to pass null parameters. If that fails, // the database server will generate the appropriate error. sqltype := sqltype or 1; new(sqlind); end; {$pop} end else AllocSQLDA(in_SQLDA,0); // Get the statement type from firebird/interbase info_request := chr(isc_info_sql_stmt_type); if isc_dsql_sql_info(@Status[0],@StatementHandle,Length(info_request), @info_request[1],sizeof(resbuf),@resbuf) <> 0 then CheckError('PrepareStatement', Status); assert(resbuf[0]=isc_info_sql_stmt_type); BlockSize:=isc_vax_integer(@resbuf[1],2); IBStatementType:=isc_vax_integer(@resbuf[3],blockSize); assert(resbuf[3+blockSize]=isc_info_end); // If the StatementType is isc_info_sql_stmt_exec_procedure then // override the statement type derived by parsing the query. // This to recognize statements like 'insert into .. returning' correctly case IBStatementType of isc_info_sql_stmt_select: FStatementType := stSelect; isc_info_sql_stmt_insert: FStatementType := stInsert; isc_info_sql_stmt_update: FStatementType := stUpdate; isc_info_sql_stmt_delete: FStatementType := stDelete; isc_info_sql_stmt_exec_procedure: FStatementType := stExecProcedure; end; FSelectable := FStatementType in [stSelect,stExecProcedure]; if FSelectable then begin if isc_dsql_describe(@Status[0], @StatementHandle, 1, SQLDA) <> 0 then CheckError('PrepareSelect', Status); if SQLDA^.SQLD > SQLDA^.SQLN then begin AllocSQLDA(SQLDA,SQLDA^.SQLD); if isc_dsql_describe(@Status[0], @StatementHandle, 1, SQLDA) <> 0 then CheckError('PrepareSelect', Status); end; FSelectable := SQLDA^.SQLD > 0; {$push} {$R-} for x := 0 to SQLDA^.SQLD - 1 do with SQLDA^.SQLVar[x] do begin if ((SQLType and not 1) = SQL_VARYING) then SQLData := AllocMem(SQLDA^.SQLVar[x].SQLLen+2) else SQLData := AllocMem(SQLDA^.SQLVar[x].SQLLen); if (SQLType and 1) = 1 then New(SQLInd); end; {$pop} end; FPrepared := True; end; end; procedure TIBConnection.UnPrepareStatement(cursor : TSQLCursor); begin with cursor as TIBcursor do if assigned(StatementHandle) Then begin if isc_dsql_free_statement(@Status[0], @StatementHandle, DSQL_Drop) <> 0 then CheckError('FreeStatement', Status); StatementHandle := nil; FPrepared := False; end; end; procedure TIBConnection.FreeSQLDABuffer(var aSQLDA : PXSQLDA); var x : Smallint; begin {$push} {$R-} if assigned(aSQLDA) then for x := 0 to aSQLDA^.SQLN - 1 do begin reAllocMem(aSQLDA^.SQLVar[x].SQLData,0); if assigned(aSQLDA^.SQLVar[x].sqlind) then begin Dispose(aSQLDA^.SQLVar[x].sqlind); aSQLDA^.SQLVar[x].sqlind := nil; end end; {$pop} end; function TIBConnection.IsDialectStored: boolean; begin result := (FDialect<>INVALID_DATA); end; procedure TIBConnection.DoConnect; const NoQuotes: TQuoteChars = (' ',' '); begin inherited DoConnect; GetDatabaseInfo; //Get db dialect, db metadata if Dialect < 3 then FieldNameQuoteChars := NoQuotes else FieldNameQuoteChars := DoubleQuotes; end; procedure TIBConnection.FreeFldBuffers(cursor : TSQLCursor); begin with cursor as TIBCursor do begin FreeSQLDABuffer(SQLDA); FreeSQLDABuffer(in_SQLDA); SetLength(FieldBinding,0); end; end; procedure TIBConnection.Execute(cursor: TSQLCursor;atransaction:tSQLtransaction; AParams : TParams); var TransactionHandle : pointer; out_SQLDA : PXSQLDA; begin TransactionHandle := aTransaction.Handle; if Assigned(APArams) and (AParams.count > 0) then SetParameters(cursor, atransaction, AParams); if LogEvent(detParamValue) then LogParams(AParams); with cursor as TIBCursor do begin if FStatementType = stExecProcedure then out_SQLDA := SQLDA else out_SQLDA := nil; if isc_dsql_execute2(@Status[0], @TransactionHandle, @StatementHandle, 1, in_SQLDA, out_SQLDA) <> 0 then CheckError('Execute', Status); end; end; procedure TIBConnection.AddFieldDefs(cursor: TSQLCursor;FieldDefs : TFieldDefs); const CS_NONE=0; CS_BINARY=1; var i : integer; PSQLVar : PXSQLVAR; TransLen, TransPrec : word; TransType : TFieldType; function GetBlobCharset(TableName,ColumnName: Pointer): smallint; var TransactionHandle: pointer; BlobDesc: TISC_BLOB_DESC; Global: array[0..31] of AnsiChar; begin TransactionHandle := TIBCursor(cursor).TransactionHandle; if isc_blob_lookup_desc(@FStatus[0], @FDatabaseHandle, @TransactionHandle, TableName, ColumnName, @BlobDesc, @Global) <> 0 then CheckError('Blob Charset', FStatus); Result := BlobDesc.blob_desc_charset; end; begin {$push} {$R-} with cursor as TIBCursor do begin setlength(FieldBinding,SQLDA^.SQLD); for i := 0 to SQLDA^.SQLD - 1 do begin PSQLVar := @SQLDA^.SQLVar[i]; TranslateFldType(PSQLVar^.SQLType, PSQLVar^.sqlsubtype, PSQLVar^.SQLLen, PSQLVar^.SQLScale, TransType, TransLen, TransPrec); // [var]char or blob column character set NONE or OCTETS overrides connection charset if (((TransType in [ftString, ftFixedChar]) and (PSQLVar^.sqlsubtype and $FF in [CS_NONE,CS_BINARY])) and not UseConnectionCharSetIfNone) or ((TransType = ftMemo) and (PSQLVar^.relname_length>0) and (PSQLVar^.sqlname_length>0) and (GetBlobCharset(@PSQLVar^.relname,@PSQLVar^.sqlname) in [CS_NONE,CS_BINARY])) then FieldDefs.Add(PSQLVar^.AliasName, TransType, TransLen, TransPrec, (PSQLVar^.sqltype and 1)=0, False, i+1, CP_NONE) else AddFieldDef(FieldDefs, i+1, PSQLVar^.AliasName, TransType, TransLen, TransPrec, True, (PSQLVar^.sqltype and 1)=0, False); FieldBinding[i] := i; end; end; {$pop} end; function TIBConnection.GetHandle: pointer; begin Result := FDatabaseHandle; end; function TIBConnection.Fetch(cursor : TSQLCursor) : boolean; var retcode : integer; begin with cursor as TIBCursor do begin if FStatementType = stExecProcedure then //do not fetch from a non-select statement, i.e. statement which has no cursor //on Firebird 2.5+ it leads to error 'Invalid cursor reference' if SQLDA^.SQLD = 0 then retcode := 100 //no more rows to retrieve else begin retcode := 0; SQLDA^.SQLD := 0; //hack: mark after first fetch end else retcode := isc_dsql_fetch(@Status[0], @StatementHandle, 1, SQLDA); if (retcode <> 0) and (retcode <> 100) then CheckError('Fetch', Status); end; Result := (retcode = 0); end; function IntPower10(e: integer): double; const PreComputedPower10: array[0..9] of integer = (1,10,100,1000,10000,100000,1000000,10000000,100000000,1000000000); var n: integer; begin n := abs(e); //exponent can't be greater than 18 if n <= 9 then Result := PreComputedPower10[n] else Result := PreComputedPower10[9] * PreComputedPower10[n-9]; if e < 0 then Result := 1 / Result; end; procedure TIBConnection.SetParameters(cursor : TSQLCursor; aTransation : TSQLTransaction; AParams : TParams); var // This should be a pointer, because the ORIGINAL variables must be modified. VSQLVar : PXSQLVAR; AParam : TParam; s : rawbytestring; i : integer; procedure SetBlobParam; var TransactionHandle : pointer; BlobId : ISC_QUAD; BlobHandle : Isc_blob_Handle; BlobSize, BlobBytesWritten : longint; begin {$push} {$R-} with cursor as TIBCursor do begin TransactionHandle := aTransation.Handle; BlobHandle := FB_API_NULLHANDLE; if isc_create_blob(@FStatus[0], @FDatabaseHandle, @TransactionHandle, @BlobHandle, @BlobId) <> 0 then CheckError('TIBConnection.CreateBlobStream', FStatus); if VSQLVar^.sqlsubtype = isc_blob_text then s := GetAsString(AParam) else s := AParam.AsString; // to avoid unwanted conversions keep it synchronized with TBlobField.GetAsVariant // best would be use AsBytes, but for now let it as is BlobSize := Length(s); BlobBytesWritten := 0; i := 0; // Write in segments of MAXBLOBSEGMENTSIZE, as that is the fastest. // We ignore BlobSegmentSize property. while BlobBytesWritten < (BlobSize-MAXBLOBSEGMENTSIZE) do begin isc_put_segment(@FStatus[0], @BlobHandle, MAXBLOBSEGMENTSIZE, @s[(i*MAXBLOBSEGMENTSIZE)+1]); inc(BlobBytesWritten,MAXBLOBSEGMENTSIZE); inc(i); end; if BlobBytesWritten <> BlobSize then isc_put_segment(@FStatus[0], @BlobHandle, BlobSize-BlobBytesWritten, @s[(i*MAXBLOBSEGMENTSIZE)+1]); if isc_close_blob(@FStatus[0], @BlobHandle) <> 0 then CheckError('TIBConnection.CreateBlobStream isc_close_blob', FStatus); Move(BlobId, VSQLVar^.SQLData^, VSQLVar^.SQLLen); end; {$pop} end; var SQLVarNr : integer; si : smallint; li : LargeInt; CurrBuff : pchar; w : word; begin {$push} {$R-} with cursor as TIBCursor do for SQLVarNr := 0 to High(ParamBinding){AParams.count-1} do begin AParam := AParams[ParamBinding[SQLVarNr]]; VSQLVar := @in_sqlda^.SQLvar[SQLVarNr]; if AParam.IsNull then VSQLVar^.SQLInd^ := -1 else begin VSQLVar^.SQLInd^ := 0; case (VSQLVar^.sqltype and not 1) of SQL_SHORT, SQL_BOOLEAN_INTERBASE : begin if VSQLVar^.sqlscale = 0 then si := AParam.AsSmallint else si := Round(AParam.AsCurrency * IntPower10(-VSQLVar^.sqlscale)); i := si; Move(i, VSQLVar^.SQLData^, VSQLVar^.SQLLen); end; SQL_LONG : begin if VSQLVar^.sqlscale = 0 then i := AParam.AsInteger else i := Round(AParam.AsFloat * IntPower10(-VSQLVar^.sqlscale)); //*any number of digits Move(i, VSQLVar^.SQLData^, VSQLVar^.SQLLen); end; SQL_INT64: begin if VSQLVar^.sqlscale = 0 then li := AParam.AsLargeInt else if AParam.DataType = ftFMTBcd then li := AParam.AsFMTBCD * IntPower10(-VSQLVar^.sqlscale) else li := Round(AParam.AsCurrency * IntPower10(-VSQLVar^.sqlscale)); Move(li, VSQLVar^.SQLData^, VSQLVar^.SQLLen); end; SQL_DOUBLE, SQL_FLOAT: SetFloat(VSQLVar^.SQLData, AParam.AsFloat, VSQLVar^.SQLLen); SQL_BLOB : SetBlobParam; SQL_VARYING, SQL_TEXT : begin Case AParam.DataType of ftDate : s := FormatDateTime('yyyy-mm-dd', AParam.AsDateTime); ftTime : s := FormatDateTime('hh":"nn":"ss', AParam.AsDateTime); ftDateTime, ftTimeStamp : s := FormatDateTime('yyyy-mm-dd hh":"nn":"ss', AParam.AsDateTime); else s := GetAsString(AParam); end; w := length(s); // a word is enough, since the max-length of a string in interbase is 32k if ((VSQLVar^.SQLType and not 1) = SQL_VARYING) then begin VSQLVar^.SQLLen := w; ReAllocMem(VSQLVar^.SQLData, VSQLVar^.SQLLen+2); CurrBuff := VSQLVar^.SQLData; move(w,CurrBuff^,sizeof(w)); inc(CurrBuff,sizeof(w)); end else begin // The buffer-length is always VSQLVar^.sqllen, nothing more, nothing less // so fill the complete buffer with valid data. Adding #0 will lead // to problems, because the #0 will be seen as a part of the (binary) string CurrBuff := VSQLVar^.SQLData; w := VSQLVar^.sqllen; s := PadRight(s,w); end; Move(s[1], CurrBuff^, w); end; SQL_TYPE_DATE, SQL_TYPE_TIME, SQL_TIMESTAMP : SetDateTime(VSQLVar^.SQLData, AParam.AsDateTime, VSQLVar^.SQLType); SQL_BOOLEAN_FIREBIRD: PByte(VSQLVar^.SQLData)^ := Byte(AParam.AsBoolean); else if (VSQLVar^.sqltype <> SQL_NULL) then DatabaseErrorFmt(SUnsupportedParameter,[FieldTypeNames[AParam.DataType]],self); end {case} end; end; {$pop} end; function TIBConnection.LoadField(cursor : TSQLCursor; FieldDef : TFieldDef; buffer : pointer; out CreateBlob : boolean) : boolean; var VSQLVar : PXSQLVAR; VarcharLen : word; CurrBuff : pchar; c : currency; AFmtBcd : tBCD; function BcdDivPower10(Dividend: largeint; e: integer): TBCD; var d: double; begin d := Dividend / IntPower10(e); Result := StrToBCD( FloatToStr(d) ); end; begin CreateBlob := False; with cursor as TIBCursor do begin {$push} {$R-} VSQLVar := @SQLDA^.SQLVar[ FieldBinding[FieldDef.FieldNo-1] ]; // Joost, 5 jan 2006: I disabled the following, since it's useful for // debugging, but it also slows things down. In principle things can only go // wrong when FieldDefs is changed while the dataset is opened. A user just // shoudn't do that. ;) (The same is done in PQConnection) // if VSQLVar^.AliasName <> FieldDef.Name then // DatabaseErrorFmt(SFieldNotFound,[FieldDef.Name],self); if assigned(VSQLVar^.SQLInd) and (VSQLVar^.SQLInd^ = -1) then result := false else begin with VSQLVar^ do if ((SQLType and not 1) = SQL_VARYING) then begin Move(SQLData^, VarcharLen, 2); CurrBuff := SQLData + 2; end else begin CurrBuff := SQLData; VarCharLen := FieldDef.Size; end; Result := true; case FieldDef.DataType of ftBCD : begin case VSQLVar^.SQLLen of 2 : c := PSmallint(CurrBuff)^ / IntPower10(-VSQLVar^.SQLScale); 4 : c := PLongint(CurrBuff)^ / IntPower10(-VSQLVar^.SQLScale); 8 : if Dialect < 3 then c := PDouble(CurrBuff)^ else c := PLargeint(CurrBuff)^ / IntPower10(-VSQLVar^.SQLScale); else Result := False; // Just to be sure, in principle this will never happen end; {case} Move(c, buffer^ , sizeof(c)); end; ftFMTBcd : begin case VSQLVar^.SQLLen of 2 : AFmtBcd := BcdDivPower10(PSmallint(CurrBuff)^, -VSQLVar^.SQLScale); 4 : AFmtBcd := BcdDivPower10(PLongint(CurrBuff)^, -VSQLVar^.SQLScale); 8 : if Dialect < 3 then AFmtBcd := PDouble(CurrBuff)^ else AFmtBcd := BcdDivPower10(PLargeint(CurrBuff)^, -VSQLVar^.SQLScale); else Result := False; // Just to be sure, in principle this will never happen end; {case} Move(AFmtBcd, buffer^ , sizeof(AFmtBcd)); end; ftInteger : begin FillByte(buffer^,sizeof(Longint),0); Move(CurrBuff^, Buffer^, VSQLVar^.SQLLen); end; ftLargeint : begin FillByte(buffer^,sizeof(LargeInt),0); Move(CurrBuff^, Buffer^, VSQLVar^.SQLLen); end; ftSmallint : begin FillByte(buffer^,sizeof(Smallint),0); Move(CurrBuff^, Buffer^, VSQLVar^.SQLLen); end; ftDate, ftTime, ftDateTime: GetDateTime(CurrBuff, Buffer, VSQLVar^.SQLType); ftString, ftFixedChar : begin Move(CurrBuff^, Buffer^, VarCharLen); PChar(Buffer + VarCharLen)^ := #0; end; ftFloat : GetFloat(CurrBuff, Buffer, VSQLVar^.SQLLen); ftBlob, ftMemo : begin // load the BlobIb in field's buffer FillByte(buffer^,sizeof(TBufBlobField),0); Move(CurrBuff^, Buffer^, VSQLVar^.SQLLen); end; ftBoolean : begin case VSQLVar^.SQLLen of 1: PWordBool(Buffer)^ := PByte(CurrBuff)^ <> 0; // Firebird 2: PWordBool(Buffer)^ := PSmallint(CurrBuff)^ <> 0; // Interbase end; end else begin result := false; databaseerrorfmt(SUnsupportedFieldType, [Fieldtypenames[FieldDef.DataType], Self]); end end; { case } end; { if/else } {$pop} end; { with cursor } end; {$DEFINE SUPPORT_MSECS} {$IFDEF SUPPORT_MSECS} const IBDateOffset = 15018; //an offset from 17 Nov 1858. IBTimeFractionsPerDay = SecsPerDay * ISC_TIME_SECONDS_PRECISION; //Number of Firebird time fractions per day {$ELSE} {$PACKRECORDS C} type TTm = 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 : PtrInt; // Seconds east of UTC __tm_zone : PAnsiChar; // Timezone abbreviation end; {$PACKRECORDS DEFAULT} {$ENDIF} procedure TIBConnection.GetDateTime(CurrBuff, Buffer : pointer; AType : integer); var {$IFNDEF SUPPORT_MSECS} CTime : TTm; // C struct time STime : TSystemTime; // System time {$ENDIF} PTime : TDateTime; // Pascal time begin case (AType and not 1) of SQL_TYPE_DATE : {$IFNDEF SUPPORT_MSECS} isc_decode_sql_date(PISC_DATE(CurrBuff), @CTime); {$ELSE} PTime := PISC_DATE(CurrBuff)^ - IBDateOffset; {$ENDIF} SQL_TYPE_TIME : {$IFNDEF SUPPORT_MSECS} isc_decode_sql_time(PISC_TIME(CurrBuff), @CTime); {$ELSE} PTime := PISC_TIME(CurrBuff)^ / IBTimeFractionsPerDay; {$ENDIF} SQL_TIMESTAMP : begin {$IFNDEF SUPPORT_MSECS} isc_decode_timestamp(PISC_TIMESTAMP(CurrBuff), @CTime); {$ELSE} PTime := ComposeDateTime( PISC_TIMESTAMP(CurrBuff)^.timestamp_date - IBDateOffset, PISC_TIMESTAMP(CurrBuff)^.timestamp_time / IBTimeFractionsPerDay ); {$ENDIF} end else Raise EIBDatabaseError.CreateFmt('Invalid parameter type for date Decode : %d',[(AType and not 1)]); end; {$IFNDEF SUPPORT_MSECS} 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); {$ENDIF} Move(PTime, Buffer^, SizeOf(PTime)); end; procedure TIBConnection.SetDateTime(CurrBuff: pointer; PTime : TDateTime; AType : integer); {$IFNDEF SUPPORT_MSECS} var CTime : TTm; // C struct time STime : TSystemTime; // System time {$ENDIF} begin {$IFNDEF SUPPORT_MSECS} DateTimeToSystemTime(PTime,STime); CTime.tm_year := STime.Year - 1900; CTime.tm_mon := STime.Month -1; CTime.tm_mday := STime.Day; CTime.tm_hour := STime.Hour; CTime.tm_min := STime.Minute; CTime.tm_sec := STime.Second; {$ENDIF} case (AType and not 1) of SQL_TYPE_DATE : {$IFNDEF SUPPORT_MSECS} isc_encode_sql_date(@CTime, PISC_DATE(CurrBuff)); {$ELSE} PISC_DATE(CurrBuff)^ := Trunc(PTime) + IBDateOffset; {$ENDIF} SQL_TYPE_TIME : {$IFNDEF SUPPORT_MSECS} isc_encode_sql_time(@CTime, PISC_TIME(CurrBuff)); {$ELSE} PISC_TIME(CurrBuff)^ := Round(abs(Frac(PTime)) * IBTimeFractionsPerDay); {$ENDIF} SQL_TIMESTAMP : begin {$IFNDEF SUPPORT_MSECS} isc_encode_timestamp(@CTime, PISC_TIMESTAMP(CurrBuff)); {$ELSE} PISC_TIMESTAMP(CurrBuff)^.timestamp_date := Trunc(PTime) + IBDateOffset; PISC_TIMESTAMP(CurrBuff)^.timestamp_time := Round(abs(Frac(PTime)) * IBTimeFractionsPerDay); if PISC_TIMESTAMP(CurrBuff)^.timestamp_time = IBTimeFractionsPerDay then begin // If PTime is for example 0.99999999999999667, the time-portion of the // TDateTime is rounded into a whole day. Firebird does not accept that. inc(PISC_TIMESTAMP(CurrBuff)^.timestamp_date); PISC_TIMESTAMP(CurrBuff)^.timestamp_time := 0; end; {$ENDIF} end else Raise EIBDatabaseError.CreateFmt('Invalid parameter type for date encode : %d',[(AType and not 1)]); end; end; function TIBConnection.GetSchemaInfoSQL(SchemaType : TSchemaType; SchemaObjectName, SchemaPattern : string) : string; var s : string; begin case SchemaType of stTables : s := 'select '+ 'rdb$relation_id as recno, '+ '''' + DatabaseName + ''' as catalog_name, '+ ''''' as schema_name, '+ 'rdb$relation_name as table_name, '+ '0 as table_type '+ 'from '+ 'rdb$relations '+ 'where '+ '(rdb$system_flag = 0 or rdb$system_flag is null) ' + // and rdb$view_blr is null 'order by rdb$relation_name'; stSysTables : s := 'select '+ 'rdb$relation_id as recno, '+ '''' + DatabaseName + ''' as catalog_name, '+ ''''' as schema_name, '+ 'rdb$relation_name as table_name, '+ '0 as table_type '+ 'from '+ 'rdb$relations '+ 'where '+ '(rdb$system_flag > 0) ' + // and rdb$view_blr is null 'order by rdb$relation_name'; stProcedures : s := 'select '+ 'rdb$procedure_id as recno, '+ '''' + DatabaseName + ''' as catalog_name, '+ ''''' as schema_name, '+ 'rdb$procedure_name as procedure_name, '+ '0 as procedure_type, '+ 'rdb$procedure_inputs as in_params, '+ 'rdb$procedure_outputs as out_params '+ 'from '+ 'rdb$procedures '+ 'WHERE '+ '(rdb$system_flag = 0 or rdb$system_flag is null)'; stColumns : s := 'SELECT '+ 'rdb$field_id as recno, '+ '''' + DatabaseName + ''' as catalog_name, '+ ''''' as schema_name, '+ 'rdb$relation_name as table_name, '+ 'r.rdb$field_name as column_name, '+ 'rdb$field_position+1 as column_position, '+ '0 as column_type, '+ 'rdb$field_type as column_datatype, '+ 'rdb$type_name as column_typename, '+ 'rdb$field_sub_type as column_subtype, '+ 'rdb$field_precision as column_precision, '+ '-rdb$field_scale as column_scale, '+ 'rdb$field_length as column_length, '+ 'case r.rdb$null_flag when 1 then 0 else 1 end as column_nullable '+ 'FROM '+ 'rdb$relation_fields r '+ 'JOIN rdb$fields f ON r.rdb$field_source=f.rdb$field_name '+ 'JOIN rdb$types t ON f.rdb$field_type=t.rdb$type AND t.rdb$field_name=''RDB$FIELD_TYPE'' '+ 'WHERE '+ '(r.rdb$system_flag = 0 or r.rdb$system_flag is null) and (rdb$relation_name = ''' + Uppercase(SchemaObjectName) + ''') ' + 'ORDER BY '+ 'r.rdb$field_name'; stSequences : s := 'SELECT ' + 'rdb$generator_id as recno,' + '''' + DatabaseName + ''' as sequence_catalog,' + ''''' as sequence_schema,' + 'rdb$generator_name as sequence_name ' + 'FROM ' + 'rdb$generators ' + 'WHERE ' + 'rdb$system_flag = 0 or rdb$system_flag is null ' + 'ORDER BY ' + 'rdb$generator_name'; else DatabaseError(SMetadataUnavailable) end; {case} result := s; end; function TIBConnection.GetNextValueSQL(const SequenceName: string; IncrementBy: Integer): string; begin Result := Format('SELECT gen_id(%s, %d) FROM RDB$DATABASE', [SequenceName, IncrementBy]); end; procedure TIBConnection.UpdateIndexDefs(IndexDefs : TIndexDefs;TableName : string); var qry : TSQLQuery; begin if not assigned(Transaction) then DatabaseError(SErrConnTransactionnSet); if (length(TableName)>2) and (TableName[1]='"') and (TableName[length(TableName)]='"') then TableName := AnsiDequotedStr(TableName, '"') else TableName := UpperCase(TableName); qry := tsqlquery.Create(nil); qry.transaction := Transaction; qry.database := Self; with qry do begin ReadOnly := True; sql.clear; sql.add('select '+ 'ind.rdb$index_name, '+ 'ind.rdb$relation_name, '+ 'ind.rdb$unique_flag, '+ 'ind_seg.rdb$field_name, '+ 'rel_con.rdb$constraint_type, '+ 'ind.rdb$index_type '+ 'from '+ 'rdb$index_segments ind_seg, '+ 'rdb$indices ind '+ 'left outer join '+ 'rdb$relation_constraints rel_con '+ 'on '+ 'rel_con.rdb$index_name = ind.rdb$index_name '+ 'where '+ '(ind_seg.rdb$index_name = ind.rdb$index_name) and '+ '(ind.rdb$relation_name=' + QuotedStr(TableName) + ') '+ 'order by '+ 'ind.rdb$index_name;'); open; end; while not qry.eof do with IndexDefs.AddIndexDef do begin Name := trim(qry.fields[0].asstring); Fields := trim(qry.Fields[3].asstring); If qry.fields[4].asstring = 'PRIMARY KEY' then options := options + [ixPrimary]; If qry.fields[2].asinteger = 1 then options := options + [ixUnique]; If qry.fields[5].asInteger = 1 then options:=options+[ixDescending]; qry.next; while (name = trim(qry.fields[0].asstring)) and (not qry.eof) do begin Fields := Fields + ';' + trim(qry.Fields[3].asstring); qry.next; end; end; qry.close; qry.free; end; procedure TIBConnection.SetFloat(CurrBuff: pointer; Dbl: Double; Size: integer); var Ext : extended; Sin : single; begin case Size of 4 : begin Sin := Dbl; Move(Sin, CurrBuff^, 4); end; 8 : begin Move(Dbl, CurrBuff^, 8); end; 10: begin Ext := Dbl; Move(Ext, CurrBuff^, 10); end; else Raise EIBDatabaseError.CreateFmt('Invalid float size for float encode : %d',[Size]); end; end; procedure TIBConnection.GetFloat(CurrBuff, Buffer: pointer; Size: Byte); var Ext : extended; Dbl : double; Sin : single; begin case Size of 4 : begin Move(CurrBuff^, Sin, 4); Dbl := Sin; end; 8 : begin Move(CurrBuff^, Dbl, 8); end; 10: begin Move(CurrBuff^, Ext, 10); Dbl := double(Ext); end; else Raise EIBDatabaseError.CreateFmt('Invalid float size for float Decode : %d',[Size]); end; Move(Dbl, Buffer^, 8); end; procedure TIBConnection.LoadBlobIntoBuffer(FieldDef: TFieldDef;ABlobBuf: PBufBlobField; cursor: TSQLCursor; ATransaction : TSQLTransaction); const isc_segstr_eof = 335544367; // It's not defined in ibase60 but in ibase40. Would it be better to define in ibase60? var blobHandle : Isc_blob_Handle; blobSegment : pointer; blobSegLen : word; TransactionHandle : pointer; blobId : PISC_QUAD; ptr : Pointer; begin // A Blob ID is a unique numeric value that references Blob data. Blob ID is stored in a field in the table // The first 4 bytes of Blob ID represent the relation id for the blob, the second four bytes represent the id of the blob within the table. // When new blob is written new Blob ID is assigned to field blobId := PISC_QUAD(@(ABlobBuf^.ConnBlobBuffer)); TransactionHandle := Atransaction.Handle; blobHandle := FB_API_NULLHANDLE; if isc_open_blob(@FStatus[0], @FDatabaseHandle, @TransactionHandle, @blobHandle, blobId) <> 0 then CheckError('TIBConnection.CreateBlobStream', FStatus); //For performance, read as much as we can, regardless of any segment size set in database. blobSegment := AllocMem(MAXBLOBSEGMENTSIZE); with ABlobBuf^.BlobBuffer^ do begin Size := 0; while (isc_get_segment(@FStatus[0], @blobHandle, @blobSegLen, MAXBLOBSEGMENTSIZE, blobSegment) = 0) do begin ReAllocMem(Buffer,Size+blobSegLen); ptr := Buffer+Size; move(blobSegment^,ptr^,blobSegLen); inc(Size,blobSegLen); end; freemem(blobSegment); if FStatus[1] = isc_segstr_eof then begin if isc_close_blob(@FStatus[0], @blobHandle) <> 0 then CheckError('TIBConnection.CreateBlobStream isc_close_blob', FStatus); end else CheckError('TIBConnection.CreateBlobStream isc_get_segment', FStatus); end; end; function TIBConnection.RowsAffected(cursor: TSQLCursor): TRowsCount; var info_request : string; resbuf : array[0..63] of byte; i : integer; BlockSize, subBlockSize : integer; SelectedRows, InsertedRows : integer; begin SelectedRows:=-1; InsertedRows:=-1; if assigned(cursor) then with cursor as TIBCursor do if assigned(StatementHandle) then begin info_request := chr(isc_info_sql_records); if isc_dsql_sql_info(@Status[0], @StatementHandle, Length(info_request), @info_request[1],sizeof(resbuf),@resbuf) <> 0 then CheckError('RowsAffected', Status); i := 0; while not (byte(resbuf[i]) in [isc_info_end,isc_info_truncated]) do begin BlockSize:=isc_vax_integer(@resbuf[i+1],2); if resbuf[i]=isc_info_sql_records then begin inc(i,3); BlockSize:=BlockSize+i; while (resbuf[i] <> isc_info_end) and (i < BlockSize) do begin subBlockSize:=isc_vax_integer(@resbuf[i+1],2); if resbuf[i] = isc_info_req_select_count then SelectedRows := isc_vax_integer(@resbuf[i+3],subBlockSize) else if resbuf[i] = isc_info_req_insert_count then InsertedRows := isc_vax_integer(@resbuf[i+3],subBlockSize); inc(i,subBlockSize+3); end; end else inc(i,BlockSize+3); end; end; if SelectedRows>0 then result:=SelectedRows else Result:=InsertedRows; end; { TIBConnectionDef } class function TIBConnectionDef.TypeName: String; begin Result:='Firebird'; end; class function TIBConnectionDef.ConnectionClass: TSQLConnectionClass; begin Result:=TIBConnection; end; class function TIBConnectionDef.Description: String; begin Result:='Connect to Firebird/Interbase directly via the client library'; end; class function TIBConnectionDef.DefaultLibraryName: String; begin {$IFDEF LinkDynamically} If UseEmbeddedFirebird then Result:=fbembedlib else Result:=fbclib; {$ELSE} Result:=''; {$ENDIF} end; class function TIBConnectionDef.LoadFunction: TLibraryLoadFunction; begin {$IFDEF LinkDynamically} Result:=@InitialiseIBase60; {$ELSE} Result:=nil; {$ENDIF} end; class function TIBConnectionDef.UnLoadFunction: TLibraryUnLoadFunction; begin {$IFDEF LinkDynamically} Result:=@ReleaseIBase60 {$ELSE} Result:=nil; {$ENDIF} end; class function TIBConnectionDef.LoadedLibraryName: string; begin {$IFDEF LinkDynamically} Result:=IBaseLoadedLibrary; {$ELSE} Result:=''; {$ENDIF} end; initialization RegisterConnection(TIBConnectionDef); finalization UnRegisterConnection(TIBConnectionDef); end.