ViewVC Help
View File | Revision Log | Show Annotations | Download File | View Changeset | Root Listing
root/public/ibx/trunk/fbintf/client/2.5/FB25Statement.pas
Revision: 349
Committed: Mon Oct 18 08:39:40 2021 UTC (3 years, 1 month ago) by tony
Content type: text/x-pascal
File size: 38837 byte(s)
Log Message:
FIxes Merged

File Contents

# Content
1 (*
2 * Firebird Interface (fbintf). The fbintf components provide a set of
3 * Pascal language bindings for the Firebird API. Although predominantly
4 * a new development they include source code taken from IBX and may be
5 * considered a derived product. This software thus also includes the copyright
6 * notice and license conditions from IBX.
7 *
8 * Except for those parts dervied from IBX, contents of this file are subject
9 * to the Initial Developer's Public License Version 1.0 (the "License"); you
10 * may not use this file except in compliance with the License. You may obtain a
11 * copy of the License here:
12 *
13 * http://www.firebirdsql.org/index.php?op=doc&id=idpl
14 *
15 * Software distributed under the License is distributed on an "AS
16 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
17 * implied. See the License for the specific language governing rights
18 * and limitations under the License.
19 *
20 * The Initial Developer of the Original Code is Tony Whyman.
21 *
22 * The Original Code is (C) 2016 Tony Whyman, MWA Software
23 * (http://www.mwasoftware.co.uk).
24 *
25 * All Rights Reserved.
26 *
27 * Contributor(s): ______________________________________.
28 *
29 *)
30 {************************************************************************}
31 { }
32 { Borland Delphi Visual Component Library }
33 { InterBase Express core components }
34 { }
35 { Copyright (c) 1998-2000 Inprise Corporation }
36 { }
37 { InterBase Express is based in part on the product }
38 { Free IB Components, written by Gregory H. Deatz for }
39 { Hoagland, Longo, Moran, Dunst & Doukas Company. }
40 { Free IB Components is used under license. }
41 { }
42 { The contents of this file are subject to the InterBase }
43 { Public License Version 1.0 (the "License"); you may not }
44 { use this file except in compliance with the License. You }
45 { may obtain a copy of the License at http://www.Inprise.com/IPL.html }
46 { Software distributed under the License is distributed on }
47 { an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either }
48 { express or implied. See the License for the specific language }
49 { governing rights and limitations under the License. }
50 { The Original Code was created by InterBase Software Corporation }
51 { and its successors. }
52 { Portions created by Inprise Corporation are Copyright (C) Inprise }
53 { Corporation. All Rights Reserved. }
54 { Contributor(s): Jeff Overcash }
55 { }
56 { IBX For Lazarus (Firebird Express) }
57 { Contributor: Tony Whyman, MWA Software http://www.mwasoftware.co.uk }
58 { Portions created by MWA Software are copyright McCallum Whyman }
59 { Associates Ltd 2011 - 2015 }
60 { }
61 {************************************************************************}
62 unit FB25Statement;
63 {$IFDEF MSWINDOWS}
64 {$DEFINE WINDOWS}
65 {$ENDIF}
66
67 {$IFDEF FPC}
68 {$mode delphi}
69 {$codepage UTF8}
70 {$interfaces COM}
71 {$ENDIF}
72
73 {This unit is hacked from IBSQL and contains the code for managing an XSQLDA and
74 SQLVars, along with statement preparation, execution and cursor management.
75 Most of the SQLVar code has been moved to unit FBSQLData. Client access is
76 provided through interface rather than direct access to the XSQLDA and XSQLVar
77 objects.}
78
79 {
80 Note on reference counted interfaces.
81 ------------------------------------
82
83 TFB25Statement manages both an input and an output SQLDA through the TIBXINPUTSQLDA
84 and TIBXOUTPUTSQLDA objects. As pure objects, these are explicitly destroyed
85 when the statement is destroyed.
86
87 However, IResultSet is an interface and is returned when a cursor is opened and
88 has a reference for the TIBXOUTPUTSQLDA. The user may discard their reference
89 to the IStatement while still using the IResultSet. This would be a problem if t
90 he underlying TFB25Statement object and its TIBXOUTPUTSQLDA is destroyed while
91 still leaving the TIBXResultSet object in place. Calls to (e.g.) FetchNext would fail.
92
93 To avoid this problem, TResultsSet objects have a reference to the IStatement
94 interface of the TFB25Statement object. Thus, as long as these "copies" exist,
95 the owning statement is not destroyed even if the user discards their reference
96 to the statement. Note: the TFB25Statement does not have a reference to the TIBXResultSet
97 interface. This way circular references are avoided.
98
99 To avoid an IResultSet interface being kept too long and no longer synchronised
100 with the query, each statement includes a prepare sequence number, incremented
101 each time the query is prepared. When the IResultSet interface is created, it
102 noted the current prepare sequence number. Whe an IResult interface is accessed
103 it checks this number against the statement's current prepare sequence number.
104 If not the same, an error is raised.
105
106 A similar strategy is used for the IMetaData, IResults and ISQLParams interfaces.
107 }
108
109 interface
110
111 uses
112 Classes, SysUtils, IB, FBClientAPI, FB25ClientAPI, FB25Transaction, FB25Attachment,
113 IBHeader, IBExternals, FBSQLData, FBOutputBlock, FBStatement, FBActivityMonitor;
114
115 type
116 TFB25Statement = class;
117 TIBXSQLDA = class;
118
119 { TIBXSQLVAR }
120
121 TIBXSQLVAR = class(TSQLVarData)
122 private
123 FStatement: TFB25Statement;
124 FFirebird25ClientAPI: TFB25ClientAPI;
125 FBlob: IBlob; {Cache references}
126 FArray: IArray;
127 FNullIndicator: short;
128 FOwnsSQLData: boolean;
129 FBlobMetaData: IBlobMetaData;
130 FArrayMetaData: IArrayMetaData;
131 FMetadataSize: short; {size of field from metadata}
132 FXSQLVAR: PXSQLVAR; { Points to the PXSQLVAR in the owner object }
133 protected
134 function GetSQLType: cardinal; override;
135 function GetSubtype: integer; override;
136 function GetAliasName: AnsiString; override;
137 function GetFieldName: AnsiString; override;
138 function GetOwnerName: AnsiString; override;
139 function GetRelationName: AnsiString; override;
140 function GetScale: integer; override;
141 function GetCharSetID: cardinal; override;
142 function GetCodePage: TSystemCodePage; override;
143 function GetCharSetWidth: integer; override;
144 function GetIsNull: Boolean; override;
145 function GetIsNullable: boolean; override;
146 function GetSQLData: PByte; override;
147 function GetDataLength: cardinal; override;
148 function GetSize: cardinal; override;
149 function GetAttachment: IAttachment; override;
150 function GetDefaultTextSQLType: cardinal; override;
151 procedure SetIsNull(Value: Boolean); override;
152 procedure SetIsNullable(Value: Boolean); override;
153 procedure SetSQLData(AValue: PByte; len: cardinal); override;
154 procedure SetScale(aValue: integer); override;
155 procedure SetDataLength(len: cardinal); override;
156 procedure SetSQLType(aValue: cardinal); override;
157 procedure SetCharSetID(aValue: cardinal); override;
158 public
159 constructor Create(aParent: TIBXSQLDA; aIndex: integer);
160 procedure FreeSQLData;
161 procedure RowChange; override;
162 function GetAsArray(Array_ID: TISC_QUAD): IArray; override;
163 function GetAsBlob(Blob_ID: TISC_QUAD; BPB: IBPB): IBlob; override;
164 function GetArrayMetaData: IArrayMetaData; override;
165 function GetBlobMetaData: IBlobMetaData; override;
166 function CreateBlob: IBlob; override;
167 procedure Initialize; override;
168
169 property Statement: TFB25Statement read FStatement;
170 property SQLType: cardinal read GetSQLType write SetSQLType;
171 end;
172
173 TIBXINPUTSQLDA = class;
174
175 { TIBXSQLDA }
176
177 TIBXSQLDA = class(TSQLDataArea)
178 private
179 FCount: Integer; {Columns in use - may be less than inherited columns}
180 FSize: Integer; {Number of TIBXSQLVARs in column list}
181 FXSQLDA: PXSQLDA;
182 FTransactionSeqNo: integer;
183 function GetRecordSize: Integer;
184 function GetXSQLDA: PXSQLDA;
185 protected
186 FStatement: TFB25Statement;
187 FFirebird25ClientAPI: TFB25ClientAPI;
188 function GetTransactionSeqNo: integer; override;
189 procedure FreeXSQLDA;
190 function GetStatement: IStatement; override;
191 function GetPrepareSeqNo: integer; override;
192 procedure SetCount(Value: Integer); override;
193 public
194 constructor Create(aStatement: TFB25Statement);
195 destructor Destroy; override;
196 function CheckStatementStatus(Request: TStatementStatus): boolean; override;
197 function ColumnsInUseCount: integer; override;
198 function GetTransaction: TFB25Transaction; virtual;
199 procedure Initialize; override;
200 function StateChanged(var ChangeSeqNo: integer): boolean; override;
201 property AsXSQLDA: PXSQLDA read GetXSQLDA;
202 property Count: Integer read FCount write SetCount;
203 property RecordSize: Integer read GetRecordSize;
204 property Statement: TFB25Statement read FStatement;
205 end;
206
207 { TIBXINPUTSQLDA }
208
209 TIBXINPUTSQLDA = class(TIBXSQLDA)
210 public
211 procedure Bind;
212 function IsInputDataArea: boolean; override;
213 end;
214
215
216 { TIBXOUTPUTSQLDA }
217
218 TIBXOUTPUTSQLDA = class(TIBXSQLDA)
219 private
220 FTransaction: TFB25Transaction; {transaction used to execute the statement}
221 public
222 procedure Bind;
223 function GetTransaction: TFB25Transaction; override;
224 procedure GetData(index: integer; var aIsNull: boolean; var len: short;
225 var data: PByte); override;
226 function IsInputDataArea: boolean; override;
227 end;
228
229 { TResultSet }
230
231 TResultSet = class(TResults,IResultSet)
232 private
233 FResults: TIBXOUTPUTSQLDA;
234 FCursorSeqNo: integer;
235 public
236 constructor Create(aResults: TIBXOUTPUTSQLDA);
237 destructor Destroy; override;
238 {IResultSet}
239 function FetchNext: boolean;
240 function GetCursorName: AnsiString;
241 function GetTransaction: ITransaction; override;
242 function IsEof: boolean;
243 procedure Close;
244 end;
245
246 { TFB25Statement }
247
248 TFB25Statement = class(TFBStatement,IStatement)
249 private
250 FDBHandle: TISC_DB_HANDLE;
251 FHandle: TISC_STMT_HANDLE;
252 FFirebird25ClientAPI: TFB25ClientAPI;
253 FSQLParams: TIBXINPUTSQLDA;
254 FSQLRecord: TIBXOUTPUTSQLDA;
255 FCursor: AnsiString; { Cursor name...}
256 FCursorSeqNo: integer;
257 procedure GetPerfCounters(var counters: TPerfStatistics);
258 protected
259 procedure CheckHandle; override;
260 procedure GetDsqlInfo(info_request: byte; buffer: ISQLInfoResults); override;
261 procedure InternalPrepare; override;
262 function InternalExecute(aTransaction: ITransaction): IResults; override;
263 function InternalOpenCursor(aTransaction: ITransaction): IResultSet; override;
264 procedure ProcessSQL(sql: AnsiString; GenerateParamNames: boolean; var processedSQL: AnsiString); override;
265 procedure FreeHandle; override;
266 procedure InternalClose(Force: boolean); override;
267 public
268 constructor Create(Attachment: TFB25Attachment; Transaction: ITransaction;
269 sql: AnsiString; aSQLDialect: integer);
270 constructor CreateWithParameterNames(Attachment: TFB25Attachment;
271 Transaction: ITransaction; sql: AnsiString; aSQLDialect: integer; GenerateParamNames: boolean;
272 CaseSensitiveParams: boolean=false);
273 destructor Destroy; override;
274 function FetchNext: boolean;
275
276 public
277 {IStatement}
278 function GetSQLParams: ISQLParams; override;
279 function GetMetaData: IMetaData; override;
280 function GetPlan: AnsiString;
281 function IsPrepared: boolean;
282 function CreateBlob(column: TColumnMetaData): IBlob; override;
283 function CreateArray(column: TColumnMetaData): IArray; override;
284 procedure SetRetainInterfaces(aValue: boolean); override;
285 property Handle: TISC_STMT_HANDLE read FHandle;
286
287 end;
288
289 implementation
290
291 uses IBUtils, FBMessages, FBBlob, FB25Blob, variants, IBErrorCodes, FBArray, FB25Array
292 {$IFDEF UNIX}, BaseUnix {$ENDIF};
293
294
295 { TIBXSQLVAR }
296
297 function TIBXSQLVAR.GetSQLType: cardinal;
298 begin
299 result := FXSQLVAR^.sqltype and (not 1);
300 end;
301
302 function TIBXSQLVAR.GetSubtype: integer;
303 begin
304 if GetSQLType = SQL_BLOB then
305 result := FXSQLVAR^.sqlsubtype
306 else
307 result := 0;
308 end;
309
310 function TIBXSQLVAR.GetAliasName: AnsiString;
311 begin
312 result := strpas(FXSQLVAR^.aliasname);
313 end;
314
315 function TIBXSQLVAR.GetFieldName: AnsiString;
316 begin
317 result := strpas(FXSQLVAR^.sqlname);
318 end;
319
320 function TIBXSQLVAR.GetOwnerName: AnsiString;
321 begin
322 result := strpas(FXSQLVAR^.ownname);
323 end;
324
325 function TIBXSQLVAR.GetRelationName: AnsiString;
326 begin
327 result := strpas(FXSQLVAR^.relname);
328 end;
329
330 function TIBXSQLVAR.GetScale: integer;
331 begin
332 if GetSQLType = SQL_BLOB then
333 result := 0
334 else
335 result := FXSQLVAR^.sqlscale;
336 end;
337
338 function TIBXSQLVAR.GetCharSetID: cardinal;
339 begin
340 result := 0;
341 case SQLType of
342 SQL_VARYING, SQL_TEXT:
343 {see http://firebirdsql.org/rlsnotesh/rlsnotes210.html}
344 result := FXSQLVAR^.sqlsubtype and $FF;
345
346 SQL_BLOB:
347 if (SQLSubType = 1) then
348 {see http://firebirdsql.org/rlsnotesh/rlsnotes210.html}
349 result := FXSQLVAR^.sqlscale and $FF;
350
351 SQL_ARRAY:
352 if (GetRelationName <> '') and (GetFieldName <> '') then
353 result := GetArrayMetaData.GetCharSetID;
354 end;
355 end;
356
357 function TIBXSQLVAR.GetCodePage: TSystemCodePage;
358 begin
359 result := CP_NONE;
360 with Statement.GetAttachment do
361 CharSetID2CodePage(GetCharSetID,result);
362 end;
363
364 function TIBXSQLVAR.GetCharSetWidth: integer;
365 begin
366 result := 1;
367 with Statement.GetAttachment DO
368 CharSetWidth(GetCharSetID,result);
369 end;
370
371 function TIBXSQLVAR.GetIsNull: Boolean;
372 begin
373 result := IsNullable and (FNullIndicator = -1);
374 end;
375
376 function TIBXSQLVAR.GetIsNullable: boolean;
377 begin
378 result := (FXSQLVAR^.sqltype and 1 = 1);
379 end;
380
381 function TIBXSQLVAR.GetSQLData: PByte;
382 begin
383 Result := FXSQLVAR^.sqldata;
384 end;
385
386 function TIBXSQLVAR.GetDataLength: cardinal;
387 begin
388 Result := FXSQLVAR^.sqllen;
389 end;
390
391 function TIBXSQLVAR.GetSize: cardinal;
392 begin
393 Result := FMetadataSize;
394 end;
395
396 function TIBXSQLVAR.GetAttachment: IAttachment;
397 begin
398 Result := FStatement.GetAttachment;
399 end;
400
401 function TIBXSQLVAR.GetArrayMetaData: IArrayMetaData;
402 begin
403 if GetSQLType <> SQL_ARRAY then
404 IBError(ibxeInvalidDataConversion,[nil]);
405
406 if FArrayMetaData = nil then
407 FArrayMetaData := TFB25ArrayMetaData.Create(FStatement.GetAttachment as TFB25Attachment,
408 FStatement.GetTransaction as TFB25Transaction,
409 GetRelationName,GetFieldName);
410 Result := FArrayMetaData;
411 end;
412
413 function TIBXSQLVAR.GetBlobMetaData: IBlobMetaData;
414 begin
415 if GetSQLType <> SQL_BLOB then
416 IBError(ibxeInvalidDataConversion,[nil]);
417
418 if FBlobMetaData = nil then
419 FBlobMetaData := TFB25BlobMetaData.Create(FStatement.GetAttachment as TFB25Attachment,
420 FStatement.GetTransaction as TFB25Transaction,
421 GetRelationName,GetFieldName,GetSubType);
422 (FBlobMetaData as TFBBlobMetaData).SetCharSetID(GetCharSetID);
423 Result := FBlobMetaData;
424 end;
425
426 function TIBXSQLVAR.GetAsArray(Array_ID: TISC_QUAD): IArray;
427 begin
428 if SQLType <> SQL_ARRAY then
429 IBError(ibxeInvalidDataConversion,[nil]);
430
431 if IsNull then
432 Result := nil
433 else
434 begin
435 if FArray = nil then
436 FArray := TFB25Array.Create(FStatement.GetAttachment as TFB25Attachment,
437 TIBXSQLDA(Parent).GetTransaction,
438 GetArrayMetaData,Array_ID);
439 Result := FArray;
440 end;
441 end;
442
443 function TIBXSQLVAR.GetAsBlob(Blob_ID: TISC_QUAD; BPB: IBPB): IBlob;
444 begin
445 if FBlob <> nil then
446 Result := FBlob
447 else
448 begin
449 if SQLType <> SQL_BLOB then
450 IBError(ibxeInvalidDataConversion, [nil]);
451 if IsNull then
452 Result := nil
453 else
454 Result := TFB25Blob.Create(FStatement.GetAttachment as TFB25Attachment,
455 TIBXSQLDA(Parent).GetTransaction,
456 GetBlobMetaData,
457 Blob_ID,BPB);
458 FBlob := Result;
459 end;
460 end;
461
462 function TIBXSQLVAR.CreateBlob: IBlob;
463 begin
464 Result := TFB25Blob.Create(FStatement.GetAttachment as TFB25Attachment,
465 FStatement.GetTransaction as TFB25Transaction,GetSubType,GetCharSetID,nil);
466 end;
467
468 procedure TIBXSQLVAR.Initialize;
469 begin
470 inherited Initialize;
471 FOwnsSQLData := true;
472 with FFirebird25ClientAPI, FXSQLVar^ do
473 begin
474 FMetadataSize := sqllen;
475 case sqltype and (not 1) of
476 SQL_TEXT, SQL_TYPE_DATE, SQL_TYPE_TIME, SQL_TIMESTAMP,
477 SQL_BLOB, SQL_ARRAY, SQL_QUAD, SQL_SHORT, SQL_BOOLEAN,
478 SQL_LONG, SQL_INT64, SQL_DOUBLE, SQL_FLOAT, SQL_D_FLOAT: begin
479 if (sqllen = 0) then
480 { Make sure you get a valid pointer anyway
481 select '' from foo }
482 IBAlloc(sqldata, 0, 1)
483 else
484 IBAlloc(sqldata, 0, sqllen)
485 end;
486 SQL_VARYING: begin
487 IBAlloc(sqldata, 0, sqllen + 2);
488 end;
489 else
490 IBError(ibxeUnknownSQLDataType, [sqltype and (not 1)])
491 end;
492 if (sqltype and 1 = 1) then
493 begin
494 sqlInd := @FNullIndicator;
495 FNullIndicator := -1;
496 end
497 else
498 sqlInd := nil;
499 end;
500 SaveMetaData;
501 end;
502
503 procedure TIBXSQLVAR.SetIsNull(Value: Boolean);
504 begin
505 if Value then
506 begin
507 IsNullable := true;
508 FNullIndicator := -1;
509 Changed;
510 end
511 else
512 if ((not Value) and IsNullable) then
513 begin
514 FNullIndicator := 0;
515 Changed;
516 end;
517 end;
518
519 procedure TIBXSQLVAR.SetIsNullable(Value: Boolean);
520 begin
521 if (Value <> IsNullable) then
522 begin
523 if Value then
524 begin
525 FXSQLVAR^.sqltype := FXSQLVAR^.sqltype or 1;
526 FNullIndicator := 0;
527 FXSQLVAR^.sqlInd := @FNullIndicator;
528 end
529 else
530 begin
531 FXSQLVAR^.sqltype := FXSQLVAR^.sqltype and (not 1);
532 FXSQLVAR^.sqlind := nil;
533 end;
534 end;
535 Changed;
536 end;
537
538 procedure TIBXSQLVAR.SetSQLData(AValue: PByte; len: cardinal);
539 begin
540 if FOwnsSQLData then
541 FreeMem(FXSQLVAR^.sqldata);
542 FXSQLVAR^.sqldata := AValue;
543 FXSQLVAR^.sqllen := len;
544 FOwnsSQLData := false;
545 Changed;
546 end;
547
548 procedure TIBXSQLVAR.SetScale(aValue: integer);
549 begin
550 FXSQLVAR^.sqlscale := aValue;
551 Changed;
552 end;
553
554 procedure TIBXSQLVAR.SetDataLength(len: cardinal);
555 begin
556 if not FOwnsSQLData then
557 FXSQLVAR^.sqldata := nil;
558 FXSQLVAR^.sqllen := len;
559 with FFirebird25ClientAPI do
560 IBAlloc(FXSQLVAR^.sqldata, 0, FXSQLVAR^.sqllen);
561 FOwnsSQLData := true;
562 Changed;
563 end;
564
565 procedure TIBXSQLVAR.SetSQLType(aValue: cardinal);
566 begin
567 FXSQLVAR^.sqltype := aValue or (FXSQLVAR^.sqltype and 1);
568 Changed;
569 end;
570
571 procedure TIBXSQLVAR.SetCharSetID(aValue: cardinal);
572 begin
573 if aValue <> GetCharSetID then
574 begin
575 case SQLType of
576 SQL_VARYING, SQL_TEXT:
577 FXSQLVAR^.sqlsubtype := (aValue and $FF) or (FXSQLVAR^.sqlsubtype and not $FF);
578
579 SQL_BLOB,
580 SQL_ARRAY:
581 IBError(ibxeInvalidDataConversion,[nil]);
582 end;
583 Changed;
584 end;
585 end;
586
587 function TIBXSQLVAR.GetDefaultTextSQLType: cardinal;
588 begin
589 Result := SQL_TEXT;
590 end;
591
592 constructor TIBXSQLVAR.Create(aParent: TIBXSQLDA; aIndex: integer);
593 begin
594 inherited Create(aParent,aIndex);
595 FStatement := aParent.Statement;
596 FFirebird25ClientAPI := aParent.FFirebird25ClientAPI;
597 end;
598
599 procedure TIBXSQLVAR.FreeSQLData;
600 begin
601 if FOwnsSQLData then
602 FreeMem(FXSQLVAR^.sqldata);
603 FXSQLVAR^.sqldata := nil;
604 FOwnsSQLData := true;
605 end;
606
607 procedure TIBXSQLVAR.RowChange;
608 begin
609 inherited RowChange;
610 FBlob := nil;
611 FArray := nil;
612 end;
613
614
615 { TResultSet }
616
617 constructor TResultSet.Create(aResults: TIBXOUTPUTSQLDA);
618 begin
619 inherited Create(aResults);
620 FResults := aResults;
621 FCursorSeqNo := aResults.FStatement.FCursorSeqNo;
622 end;
623
624 destructor TResultSet.Destroy;
625 begin
626 Close;
627 inherited Destroy;
628 end;
629
630 function TResultSet.FetchNext: boolean;
631 var i: integer;
632 begin
633 CheckActive;
634 Result := FResults.FStatement.FetchNext;
635 if Result then
636 for i := 0 to getCount - 1 do
637 FResults.Column[i].RowChange;
638 end;
639
640 function TResultSet.GetCursorName: AnsiString;
641 begin
642 Result := FResults.FStatement.FCursor;
643 end;
644
645 function TResultSet.GetTransaction: ITransaction;
646 begin
647 Result := FResults.GetTransaction;
648 end;
649
650 function TResultSet.IsEof: boolean;
651 begin
652 Result := FResults.FStatement.FEof;
653 end;
654
655 procedure TResultSet.Close;
656 begin
657 if FCursorSeqNo = FResults.FStatement.FCursorSeqNo then
658 FResults.FStatement.Close;
659 end;
660
661 { TIBXINPUTSQLDA }
662
663 procedure TIBXINPUTSQLDA.Bind;
664 begin
665 if Count = 0 then
666 Count := 1;
667 with FFirebird25ClientAPI do
668 begin
669 if (FXSQLDA <> nil) then
670 if isc_dsql_describe_bind(StatusVector, @(FStatement.Handle), FStatement.SQLDialect,
671 FXSQLDA) > 0 then
672 IBDataBaseError;
673
674 if FXSQLDA^.sqld > FXSQLDA^.sqln then
675 begin
676 Count := FXSQLDA^.sqld;
677 if isc_dsql_describe_bind(StatusVector, @(FStatement.Handle), FStatement.SQLDialect,
678 FXSQLDA) > 0 then
679 IBDataBaseError;
680 end
681 else
682 if FXSQLDA^.sqld = 0 then
683 Count := 0;
684 end;
685 Initialize;
686 end;
687
688 function TIBXINPUTSQLDA.IsInputDataArea: boolean;
689 begin
690 Result := true;
691 end;
692
693 { TIBXOUTPUTSQLDA }
694
695 procedure TIBXOUTPUTSQLDA.Bind;
696 begin
697 { Allocate an initial output descriptor (with one column) }
698 Count := 1;
699 with FFirebird25ClientAPI do
700 begin
701 { Using isc_dsql_describe, get the right size for the columns... }
702 if isc_dsql_describe(StatusVector, @(FStatement.Handle), FStatement.SQLDialect, FXSQLDA) > 0 then
703 IBDataBaseError;
704
705 if FXSQLDA^.sqld > FXSQLDA^.sqln then
706 begin
707 Count := FXSQLDA^.sqld;
708 if isc_dsql_describe(StatusVector, @(FStatement.Handle), FStatement.SQLDialect, FXSQLDA) > 0 then
709 IBDataBaseError;
710 end
711 else
712 if FXSQLDA^.sqld = 0 then
713 Count := 0;
714 end;
715 Initialize;
716 SetUniqueRelationName;
717 end;
718
719 function TIBXOUTPUTSQLDA.GetTransaction: TFB25Transaction;
720 begin
721 Result := FTransaction;
722 end;
723
724 procedure TIBXOUTPUTSQLDA.GetData(index: integer; var aIsNull:boolean; var len: short;
725 var data: PByte);
726 begin
727 with TIBXSQLVAR(Column[index]), FXSQLVAR^ do
728 begin
729 aIsNull := (sqltype and 1 = 1) and (FNullIndicator = -1);
730 data := sqldata;
731 len := sqllen;
732 if not IsNull and ((sqltype and (not 1)) = SQL_VARYING) then
733 begin
734 with FFirebird25ClientAPI do
735 len := DecodeInteger(data,2);
736 Inc(data,2);
737 end;
738 end;
739 end;
740
741 function TIBXOUTPUTSQLDA.IsInputDataArea: boolean;
742 begin
743 Result := false;
744 end;
745
746 { TIBXSQLDA }
747 constructor TIBXSQLDA.Create(aStatement: TFB25Statement);
748 begin
749 inherited Create;
750 FStatement := aStatement;
751 FFirebird25ClientAPI := aStatement.FFirebird25ClientAPI;
752 FSize := 0;
753 // writeln('Creating ',ClassName);
754 end;
755
756 destructor TIBXSQLDA.Destroy;
757 begin
758 FreeXSQLDA;
759 // writeln('Destroying ',ClassName);
760 inherited Destroy;
761 end;
762
763 function TIBXSQLDA.CheckStatementStatus(Request: TStatementStatus): boolean;
764 begin
765 Result := false;
766 case Request of
767 ssPrepared:
768 Result := FStatement.IsPrepared;
769
770 ssExecuteResults:
771 Result :=FStatement.FSingleResults;
772
773 ssCursorOpen:
774 Result := FStatement.FOpen;
775
776 ssBOF:
777 Result := FStatement.FBOF;
778
779 ssEOF:
780 Result := FStatement.FEOF;
781 end;
782 end;
783
784 function TIBXSQLDA.ColumnsInUseCount: integer;
785 begin
786 Result := FCount;
787 end;
788
789 function TIBXSQLDA.GetRecordSize: Integer;
790 begin
791 result := SizeOf(TIBXSQLDA) + XSQLDA_LENGTH(FSize);
792 end;
793
794 function TIBXSQLDA.GetXSQLDA: PXSQLDA;
795 begin
796 result := FXSQLDA;
797 end;
798
799 function TIBXSQLDA.GetTransactionSeqNo: integer;
800 begin
801 Result := FTransactionSeqNo;
802 end;
803
804 procedure TIBXSQLDA.Initialize;
805 begin
806 if FXSQLDA <> nil then
807 inherited Initialize;
808 end;
809
810 function TIBXSQLDA.StateChanged(var ChangeSeqNo: integer): boolean;
811 begin
812 Result := FStatement.ChangeSeqNo <> ChangeSeqNo;
813 if Result then
814 ChangeSeqNo := FStatement.ChangeSeqNo;
815 end;
816
817 function TIBXSQLDA.GetTransaction: TFB25Transaction;
818 begin
819 Result := FStatement.GetTransaction as TFB25Transaction;
820 end;
821
822 procedure TIBXSQLDA.SetCount(Value: Integer);
823 var
824 i, OldSize: Integer;
825 p : PXSQLVAR;
826 begin
827 FCount := Value;
828 if FCount = 0 then
829 FUniqueRelationName := ''
830 else
831 begin
832 if FSize > 0 then
833 OldSize := XSQLDA_LENGTH(FSize)
834 else
835 OldSize := 0;
836 if Count > FSize then
837 begin
838 FFirebird25ClientAPI.IBAlloc(FXSQLDA, OldSize, XSQLDA_LENGTH(Count));
839 SetLength(FColumnList, FCount);
840 FXSQLDA^.version := SQLDA_VERSION1;
841 p := @FXSQLDA^.sqlvar[0];
842 for i := 0 to Count - 1 do
843 begin
844 if i >= FSize then
845 FColumnList[i] := TIBXSQLVAR.Create(self,i);
846 TIBXSQLVAR(Column[i]).FXSQLVAR := p;
847 p := Pointer(PAnsiChar(p) + sizeof(FXSQLDA^.sqlvar));
848 end;
849 FSize := inherited Count;
850 end;
851 if FSize > 0 then
852 begin
853 FXSQLDA^.sqln := Value;
854 FXSQLDA^.sqld := Value;
855 end;
856 end;
857 end;
858
859 procedure TIBXSQLDA.FreeXSQLDA;
860 var i: integer;
861 begin
862 if FXSQLDA <> nil then
863 begin
864 // writeln('SQLDA Cleanup');
865 for i := 0 to Count - 1 do
866 TIBXSQLVAR(Column[i]).FreeSQLData;
867 FreeMem(FXSQLDA);
868 FXSQLDA := nil;
869 end;
870 for i := 0 to FSize - 1 do
871 TIBXSQLVAR(Column[i]).Free;
872 SetLength(FColumnList,0);
873 FSize := 0;
874 end;
875
876 function TIBXSQLDA.GetStatement: IStatement;
877 begin
878 Result := FStatement;
879 end;
880
881 function TIBXSQLDA.GetPrepareSeqNo: integer;
882 begin
883 Result := FStatement.FPrepareSeqNo;
884 end;
885
886 { TFB25Statement }
887
888 procedure TFB25Statement.GetPerfCounters(var counters: TPerfStatistics);
889 var DBInfo: IDBInformation;
890 i: integer;
891 {$IFDEF UNIX}
892 times: tms;
893 {$ENDIF}
894 begin
895 {$IFDEF UNIX}
896 FpTimes(times);
897 counters[psUserTime] := times.tms_utime;
898 {$ELSE}
899 counters[psUserTime] := 0;
900 {$ENDIF}
901 counters[psRealTime] := TimeStampToMSecs(DateTimeToTimeStamp(Now));
902
903 DBInfo := GetAttachment.GetDBInformation([isc_info_reads,isc_info_writes,
904 isc_info_fetches, isc_info_num_buffers, isc_info_current_memory,
905 isc_info_max_memory]);
906 if DBInfo <> nil then
907 begin
908 for i := 0 to DBInfo.Count - 1 do
909 with DBInfo[i] do
910 case getItemType of
911 isc_info_reads:
912 counters[psReads] := AsInteger;
913 isc_info_writes:
914 counters[psWrites] := AsInteger;
915 isc_info_fetches:
916 counters[psFetches] := AsInteger;
917 isc_info_num_buffers:
918 counters[psBuffers] := AsInteger;
919 isc_info_current_memory:
920 counters[psCurrentMemory] := AsInteger;
921 isc_info_max_memory:
922 counters[psMaxMemory] := AsInteger;
923 end;
924 end;
925 end;
926
927 procedure TFB25Statement.CheckHandle;
928 begin
929 if FHandle = nil then
930 IBError(ibxeInvalidStatementHandle,[nil]);
931 end;
932
933 procedure TFB25Statement.GetDsqlInfo(info_request: byte; buffer: ISQLInfoResults
934 );
935 begin
936 with FFirebird25ClientAPI, buffer as TSQLInfoResultsBuffer do
937 if isc_dsql_sql_info(StatusVector, @(FHandle), 1, @info_request,
938 GetBufSize, Buffer) > 0 then
939 IBDatabaseError;
940 end;
941
942 procedure TFB25Statement.InternalPrepare;
943 var
944 RB: ISQLInfoResults;
945 TRHandle: TISC_TR_HANDLE;
946 begin
947 if FPrepared then
948 Exit;
949 if (FSQL = '') then
950 IBError(ibxeEmptyQuery, [nil]);
951 try
952 CheckTransaction(FTransactionIntf);
953 with FFirebird25ClientAPI do
954 begin
955 Call(isc_dsql_alloc_statement2(StatusVector, @(FDBHandle),
956 @FHandle), True);
957 TRHandle := (FTransactionIntf as TFB25Transaction).Handle;
958 if FHasParamNames then
959 begin
960 if FProcessedSQL = '' then
961 ProcessSQL(FSQL,FGenerateParamNames,FProcessedSQL);
962 Call(isc_dsql_prepare(StatusVector, @(TRHandle), @FHandle, 0,
963 PAnsiChar(FProcessedSQL), FSQLDialect, nil), True);
964 end
965 else
966 Call(isc_dsql_prepare(StatusVector, @(TRHandle), @FHandle, 0,
967 PAnsiChar(FSQL), FSQLDialect, nil), True);
968 end;
969 { After preparing the statement, query the stmt type and possibly
970 create a FSQLRecord "holder" }
971 { Get the type of the statement }
972 RB := GetDsqlInfo(isc_info_sql_stmt_type);
973 if RB.Count > 0 then
974 FSQLStatementType := TIBSQLStatementTypes(RB[0].GetAsInteger)
975 else
976 FSQLStatementType := SQLUnknown;
977
978 case FSQLStatementType of
979 SQLGetSegment,
980 SQLPutSegment,
981 SQLStartTransaction: begin
982 FreeHandle;
983 IBError(ibxeNotPermitted, [nil]);
984 end;
985 SQLCommit,
986 SQLRollback,
987 SQLDDL, SQLSetGenerator,
988 SQLInsert, SQLUpdate, SQLDelete, SQLSelect, SQLSelectForUpdate,
989 SQLExecProcedure:
990 begin
991 {set up input sqlda}
992 FSQLParams.Bind;
993
994 {setup output sqlda}
995 if FSQLStatementType in [SQLSelect, SQLSelectForUpdate,
996 SQLExecProcedure] then
997 FSQLRecord.Bind;
998 end;
999 end;
1000 except
1001 on E: Exception do begin
1002 if (FHandle <> nil) then
1003 FreeHandle;
1004 if E is EIBInterBaseError then
1005 E.Message := E.Message + sSQLErrorSeparator + FSQL;
1006 raise;
1007 end;
1008 end;
1009 FPrepared := true;
1010 FSingleResults := false;
1011 if RetainInterfaces then
1012 begin
1013 SetRetainInterfaces(false);
1014 SetRetainInterfaces(true);
1015 end;
1016 Inc(FPrepareSeqNo);
1017 Inc(FChangeSeqNo);
1018 with FTransactionIntf as TFB25Transaction do
1019 begin
1020 FSQLParams.FTransactionSeqNo := TransactionSeqNo;
1021 FSQLRecord.FTransactionSeqNo := TransactionSeqNo;
1022 end;
1023 end;
1024
1025 function TFB25Statement.InternalExecute(aTransaction: ITransaction): IResults;
1026 var TRHandle: TISC_TR_HANDLE;
1027 begin
1028 Result := nil;
1029 FBOF := false;
1030 FEOF := false;
1031 FSingleResults := false;
1032 CheckTransaction(aTransaction);
1033 if not FPrepared then
1034 InternalPrepare;
1035 CheckHandle;
1036 if aTransaction <> FTransactionIntf then
1037 AddMonitor(aTransaction as TFB25Transaction);
1038 if FStaleReferenceChecks and (FSQLParams.FTransactionSeqNo < (FTransactionIntf as TFB25transaction).TransactionSeqNo) then
1039 IBError(ibxeInterfaceOutofDate,[nil]);
1040
1041 try
1042 TRHandle := (aTransaction as TFB25Transaction).Handle;
1043 with FFirebird25ClientAPI do
1044 begin
1045 if FCollectStatistics then
1046 GetPerfCounters(FBeforeStats);
1047
1048 case FSQLStatementType of
1049 SQLSelect:
1050 IBError(ibxeIsAExecuteProcedure,[]);
1051
1052 SQLExecProcedure:
1053 begin
1054 Call(isc_dsql_execute2(StatusVector,
1055 @(TRHandle),
1056 @FHandle,
1057 SQLDialect,
1058 FSQLParams.AsXSQLDA,
1059 FSQLRecord.AsXSQLDA), True);
1060 Result := TResults.Create(FSQLRecord);
1061 FSingleResults := true;
1062 end
1063 else
1064 Call(isc_dsql_execute(StatusVector,
1065 @(TRHandle),
1066 @FHandle,
1067 SQLDialect,
1068 FSQLParams.AsXSQLDA), True);
1069
1070 end;
1071 if FCollectStatistics then
1072 begin
1073 GetPerfCounters(FAfterStats);
1074 FStatisticsAvailable := true;
1075 end;
1076 end;
1077 finally
1078 if aTransaction <> FTransactionIntf then
1079 RemoveMonitor(aTransaction as TFB25Transaction);
1080 end;
1081 FExecTransactionIntf := aTransaction;
1082 FSQLRecord.FTransaction := aTransaction as TFB25Transaction;
1083 FSQLRecord.FTransactionSeqNo := FSQLRecord.FTransaction.TransactionSeqNo;
1084 Inc(FChangeSeqNo);
1085 end;
1086
1087 function TFB25Statement.InternalOpenCursor(aTransaction: ITransaction
1088 ): IResultSet;
1089 var TRHandle: TISC_TR_HANDLE;
1090 GUID : TGUID;
1091 begin
1092 if FSQLStatementType <> SQLSelect then
1093 IBError(ibxeIsASelectStatement,[]);
1094
1095 CheckTransaction(aTransaction);
1096 if not FPrepared then
1097 InternalPrepare;
1098 CheckHandle;
1099 if aTransaction <> FTransactionIntf then
1100 AddMonitor(aTransaction as TFB25Transaction);
1101 if FStaleReferenceChecks and (FSQLParams.FTransactionSeqNo < (FTransactionIntf as TFB25transaction).TransactionSeqNo) then
1102 IBError(ibxeInterfaceOutofDate,[nil]);
1103
1104 with FFirebird25ClientAPI do
1105 begin
1106 if FCollectStatistics then
1107 GetPerfCounters(FBeforeStats);
1108
1109 TRHandle := (aTransaction as TFB25Transaction).Handle;
1110 Call(isc_dsql_execute2(StatusVector,
1111 @(TRHandle),
1112 @FHandle,
1113 SQLDialect,
1114 FSQLParams.AsXSQLDA,
1115 nil), True);
1116 if FCursor = '' then
1117 begin
1118 CreateGuid(GUID);
1119 FCursor := GUIDToString(GUID);
1120 Call(
1121 isc_dsql_set_cursor_name(StatusVector, @FHandle, PAnsiChar(FCursor), 0),
1122 True);
1123 end;
1124
1125 if FCollectStatistics then
1126 begin
1127 GetPerfCounters(FAfterStats);
1128 FStatisticsAvailable := true;
1129 end;
1130 end;
1131 Inc(FCursorSeqNo);
1132 FSingleResults := false;
1133 FOpen := True;
1134 FExecTransactionIntf := aTransaction;
1135 FBOF := true;
1136 FEOF := false;
1137 FSQLRecord.FTransaction := aTransaction as TFB25Transaction;
1138 FSQLRecord.FTransactionSeqNo := FSQLRecord.FTransaction.TransactionSeqNo;
1139 Result := TResultSet.Create(FSQLRecord);
1140 Inc(FChangeSeqNo);
1141 end;
1142
1143 procedure TFB25Statement.ProcessSQL(sql: AnsiString; GenerateParamNames: boolean;
1144 var processedSQL: AnsiString);
1145 begin
1146 FSQLParams.PreprocessSQL(sql,GenerateParamNames, processedSQL);
1147 end;
1148
1149 procedure TFB25Statement.FreeHandle;
1150 var
1151 isc_res: ISC_STATUS;
1152 begin
1153 Close;
1154 ReleaseInterfaces;
1155 try
1156 if FHandle <> nil then
1157 with FFirebird25ClientAPI do
1158 begin
1159 isc_res :=
1160 Call(isc_dsql_free_statement(StatusVector, @FHandle, DSQL_drop), False);
1161 if (StatusVector^ = 1) and (isc_res > 0) and (isc_res <> isc_bad_stmt_handle) then
1162 IBDataBaseError;
1163 end;
1164 finally
1165 FHandle := nil;
1166 FCursor := '';
1167 FPrepared := false;
1168 end;
1169 end;
1170
1171 procedure TFB25Statement.InternalClose(Force: boolean);
1172 var
1173 isc_res: ISC_STATUS;
1174 begin
1175 if (FHandle <> nil) and (SQLStatementType = SQLSelect) and FOpen then
1176 try
1177 with FFirebird25ClientAPI do
1178 begin
1179 isc_res := Call(
1180 isc_dsql_free_statement(StatusVector, @FHandle, DSQL_close),
1181 False);
1182 if not Force and (StatusVector^ = 1) and (isc_res > 0) and
1183 not getStatus.CheckStatusVector(
1184 [isc_bad_stmt_handle, isc_dsql_cursor_close_err]) then
1185 IBDatabaseError;
1186 end;
1187 finally
1188 if (FSQLRecord.FTransaction <> nil) and (FSQLRecord.FTransaction <> (FTransactionIntf as TFB25Transaction)) then
1189 RemoveMonitor(FSQLRecord.FTransaction);
1190 FOpen := False;
1191 FExecTransactionIntf := nil;
1192 FSQLRecord.FTransaction := nil;
1193 Inc(FChangeSeqNo);
1194 end;
1195 end;
1196
1197 constructor TFB25Statement.Create(Attachment: TFB25Attachment;
1198 Transaction: ITransaction; sql: AnsiString; aSQLDialect: integer);
1199 begin
1200 inherited Create(Attachment,Transaction,sql,aSQLDialect);
1201 FDBHandle := Attachment.Handle;
1202 FFirebird25ClientAPI := Attachment.Firebird25ClientAPI;
1203 OnDatabaseError := FFirebird25ClientAPI.IBDataBaseError;
1204 FSQLParams := TIBXINPUTSQLDA.Create(self);
1205 FSQLRecord := TIBXOUTPUTSQLDA.Create(self);
1206 InternalPrepare;
1207 end;
1208
1209 constructor TFB25Statement.CreateWithParameterNames(
1210 Attachment: TFB25Attachment; Transaction: ITransaction; sql: AnsiString;
1211 aSQLDialect: integer; GenerateParamNames: boolean;
1212 CaseSensitiveParams: boolean);
1213 begin
1214 inherited CreateWithParameterNames(Attachment,Transaction,sql,aSQLDialect,GenerateParamNames);
1215 FDBHandle := Attachment.Handle;
1216 FFirebird25ClientAPI := Attachment.Firebird25ClientAPI;
1217 OnDatabaseError := FFirebird25ClientAPI.IBDataBaseError;
1218 FSQLParams := TIBXINPUTSQLDA.Create(self);
1219 FSQLParams.CaseSensitiveParams := CaseSensitiveParams;
1220 FSQLRecord := TIBXOUTPUTSQLDA.Create(self);
1221 InternalPrepare;
1222 end;
1223
1224 destructor TFB25Statement.Destroy;
1225 begin
1226 inherited Destroy;
1227 if assigned(FSQLParams) then FSQLParams.Free;
1228 if assigned(FSQLRecord) then FSQLRecord.Free;
1229 end;
1230
1231 function TFB25Statement.FetchNext: boolean;
1232 var
1233 fetch_res: ISC_STATUS;
1234 begin
1235 result := false;
1236 if not FOpen then
1237 IBError(ibxeSQLClosed, [nil]);
1238 if FEOF then
1239 IBError(ibxeEOF,[nil]);
1240
1241 with FFirebird25ClientAPI do
1242 begin
1243 { Go to the next record... }
1244 fetch_res :=
1245 Call(isc_dsql_fetch(StatusVector, @FHandle, SQLDialect, FSQLRecord.AsXSQLDA), False);
1246 if (fetch_res = 100) or (getStatus.CheckStatusVector([isc_dsql_cursor_err])) then
1247 begin
1248 FBOF := false;
1249 FEOF := true;
1250 Exit; {End of File}
1251 end
1252 else
1253 if (fetch_res > 0) then
1254 begin
1255 try
1256 IBDataBaseError;
1257 except
1258 Close;
1259 raise;
1260 end;
1261 end
1262 else
1263 begin
1264 FBOF := false;
1265 result := true;
1266 end;
1267 if FCollectStatistics then
1268 begin
1269 GetPerfCounters(FAfterStats);
1270 FStatisticsAvailable := true;
1271 end;
1272 end;
1273 FSQLRecord.RowChange;
1274 if FEOF then
1275 Inc(FChangeSeqNo);
1276 end;
1277
1278 function TFB25Statement.GetSQLParams: ISQLParams;
1279 begin
1280 CheckHandle;
1281 if not HasInterface(0) then
1282 AddInterface(0,TSQLParams.Create(FSQLParams));
1283 Result := TSQLParams(GetInterface(0));
1284 end;
1285
1286 function TFB25Statement.GetMetaData: IMetaData;
1287 begin
1288 CheckHandle;
1289 if not HasInterface(1) then
1290 AddInterface(1, TMetaData.Create(FSQLRecord));
1291 Result := TMetaData(GetInterface(1));
1292 end;
1293
1294 function TFB25Statement.GetPlan: AnsiString;
1295 var
1296 RB: ISQLInfoResults;
1297 begin
1298 if (not (FSQLStatementType in [SQLSelect, SQLSelectForUpdate,
1299 {TODO: SQLExecProcedure, }
1300 SQLUpdate, SQLDelete])) then
1301 result := ''
1302 else
1303 begin
1304 RB := TSQLInfoResultsBuffer.Create(FFirebird25ClientAPI,4*4096);
1305 GetDsqlInfo(isc_info_sql_get_plan,RB);
1306 if RB.Count > 0 then
1307 Result := RB[0].GetAsString;
1308 end;
1309 end;
1310
1311 function TFB25Statement.CreateBlob(column: TColumnMetaData): IBlob;
1312 begin
1313 if assigned(column) and (column.SQLType <> SQL_Blob) then
1314 IBError(ibxeNotABlob,[nil]);
1315 Result := TFB25Blob.Create(GetAttachment as TFB25Attachment,FExecTransactionIntf as TFB25Transaction,
1316 column.GetBlobMetaData,nil);
1317 end;
1318
1319 function TFB25Statement.CreateArray(column: TColumnMetaData): IArray;
1320 begin
1321 if assigned(column) and (column.SQLType <> SQL_ARRAY) then
1322 IBError(ibxeNotAnArray,[nil]);
1323 Result := TFB25Array.Create(GetAttachment as TFB25Attachment,FExecTransactionIntf as TFB25Transaction,
1324 column.GetArrayMetaData);
1325 end;
1326
1327 procedure TFB25Statement.SetRetainInterfaces(aValue: boolean);
1328 begin
1329 inherited SetRetainInterfaces(aValue);
1330 if HasInterface(1) then
1331 TMetaData(GetInterface(1)).RetainInterfaces := aValue;
1332 if HasInterface(0) then
1333 TSQLParams(GetInterface(0)).RetainInterfaces := aValue;
1334 end;
1335
1336 function TFB25Statement.IsPrepared: boolean;
1337 begin
1338 Result := FHandle <> nil;
1339 end;
1340
1341 end.
1342