ViewVC Help
View File | Revision Log | Show Annotations | Download File | View Changeset | Root Listing
root/public/ibx/branches/journaling/fbintf/client/2.5/FB25Statement.pas
Revision: 60
Committed: Mon Mar 27 15:21:02 2017 UTC (7 years ago) by tony
Content type: text/x-pascal
Original Path: ibx/trunk/fbintf/client/2.5/FB25Statement.pas
File size: 36949 byte(s)
Log Message:

File Contents

# User Rev Content
1 tony 45 (*
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 tony 56 {$IFDEF MSWINDOWS}
64     {$DEFINE WINDOWS}
65     {$ENDIF}
66 tony 45
67     {$IFDEF FPC}
68 tony 56 {$mode delphi}
69 tony 45 {$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     FBlob: IBlob; {Cache references}
125     FArray: IArray;
126     FNullIndicator: short;
127     FOwnsSQLData: boolean;
128     FBlobMetaData: IBlobMetaData;
129     FArrayMetaData: IArrayMetaData;
130     FXSQLVAR: PXSQLVAR; { Points to the PXSQLVAR in the owner object }
131     protected
132     function GetSQLType: cardinal; override;
133     function GetSubtype: integer; override;
134 tony 56 function GetAliasName: AnsiString; override;
135     function GetFieldName: AnsiString; override;
136     function GetOwnerName: AnsiString; override;
137     function GetRelationName: AnsiString; override;
138 tony 45 function GetScale: integer; override;
139     function GetCharSetID: cardinal; override;
140     function GetCodePage: TSystemCodePage; override;
141     function GetIsNull: Boolean; override;
142     function GetIsNullable: boolean; override;
143 tony 56 function GetSQLData: PByte; override;
144 tony 45 function GetDataLength: cardinal; override;
145     procedure SetIsNull(Value: Boolean); override;
146     procedure SetIsNullable(Value: Boolean); override;
147 tony 56 procedure SetSQLData(AValue: PByte; len: cardinal); override;
148 tony 45 procedure SetScale(aValue: integer); override;
149     procedure SetDataLength(len: cardinal); override;
150     procedure SetSQLType(aValue: cardinal); override;
151     procedure SetCharSetID(aValue: cardinal); override;
152     public
153     constructor Create(aParent: TIBXSQLDA; aIndex: integer);
154     procedure FreeSQLData;
155     procedure RowChange; override;
156     function GetAsArray(Array_ID: TISC_QUAD): IArray; override;
157     function GetAsBlob(Blob_ID: TISC_QUAD; BPB: IBPB): IBlob; override;
158     function GetArrayMetaData: IArrayMetaData; override;
159     function GetBlobMetaData: IBlobMetaData; override;
160     function CreateBlob: IBlob; override;
161     procedure Initialize; override;
162    
163     property Statement: TFB25Statement read FStatement;
164     end;
165    
166     TIBXINPUTSQLDA = class;
167    
168     { TIBXSQLDA }
169    
170     TIBXSQLDA = class(TSQLDataArea)
171     private
172     FCount: Integer; {Columns in use - may be less than inherited columns}
173     FSize: Integer; {Number of TIBXSQLVARs in column list}
174     FXSQLDA: PXSQLDA;
175     FTransactionSeqNo: integer;
176     function GetRecordSize: Integer;
177     function GetXSQLDA: PXSQLDA;
178     protected
179     FStatement: TFB25Statement;
180     function GetTransactionSeqNo: integer; override;
181     procedure FreeXSQLDA;
182     function GetStatement: IStatement; override;
183     function GetPrepareSeqNo: integer; override;
184     procedure SetCount(Value: Integer); override;
185     public
186     constructor Create(aStatement: TFB25Statement);
187     destructor Destroy; override;
188     function CheckStatementStatus(Request: TStatementStatus): boolean; override;
189     function ColumnsInUseCount: integer; override;
190     function GetTransaction: TFB25Transaction; virtual;
191     procedure Initialize; override;
192     function StateChanged(var ChangeSeqNo: integer): boolean; override;
193     property AsXSQLDA: PXSQLDA read GetXSQLDA;
194     property Count: Integer read FCount write SetCount;
195     property RecordSize: Integer read GetRecordSize;
196     property Statement: TFB25Statement read FStatement;
197     end;
198    
199     { TIBXINPUTSQLDA }
200    
201     TIBXINPUTSQLDA = class(TIBXSQLDA)
202     public
203     procedure Bind;
204     function IsInputDataArea: boolean; override;
205     end;
206    
207    
208     { TIBXOUTPUTSQLDA }
209    
210     TIBXOUTPUTSQLDA = class(TIBXSQLDA)
211     private
212     FTransaction: TFB25Transaction; {transaction used to execute the statement}
213     public
214     procedure Bind;
215     function GetTransaction: TFB25Transaction; override;
216     procedure GetData(index: integer; var aIsNull: boolean; var len: short;
217 tony 56 var data: PByte); override;
218 tony 45 function IsInputDataArea: boolean; override;
219     end;
220    
221     { TResultSet }
222    
223     TResultSet = class(TResults,IResultSet)
224     private
225     FResults: TIBXOUTPUTSQLDA;
226     FCursorSeqNo: integer;
227     public
228     constructor Create(aResults: TIBXOUTPUTSQLDA);
229     destructor Destroy; override;
230     {IResultSet}
231     function FetchNext: boolean;
232 tony 56 function GetCursorName: AnsiString;
233 tony 45 function GetTransaction: ITransaction; override;
234     function IsEof: boolean;
235     procedure Close;
236     end;
237    
238     { TFB25Statement }
239    
240     TFB25Statement = class(TFBStatement,IStatement)
241     private
242     FDBHandle: TISC_DB_HANDLE;
243     FHandle: TISC_STMT_HANDLE;
244     FSQLParams: TIBXINPUTSQLDA;
245     FSQLRecord: TIBXOUTPUTSQLDA;
246 tony 56 FCursor: AnsiString; { Cursor name...}
247 tony 45 FCursorSeqNo: integer;
248 tony 47 procedure GetPerfCounters(var counters: TPerfStatistics);
249 tony 45 protected
250     procedure CheckHandle; override;
251     procedure GetDsqlInfo(info_request: byte; buffer: ISQLInfoResults); override;
252     procedure InternalPrepare; override;
253     function InternalExecute(aTransaction: ITransaction): IResults; override;
254     function InternalOpenCursor(aTransaction: ITransaction): IResultSet; override;
255     procedure FreeHandle; override;
256     procedure InternalClose(Force: boolean); override;
257     public
258     constructor Create(Attachment: TFB25Attachment; Transaction: ITransaction;
259 tony 56 sql: AnsiString; aSQLDialect: integer);
260 tony 45 constructor CreateWithParameterNames(Attachment: TFB25Attachment;
261 tony 56 Transaction: ITransaction; sql: AnsiString; aSQLDialect: integer; GenerateParamNames: boolean);
262 tony 45 destructor Destroy; override;
263     function FetchNext: boolean;
264    
265     public
266     {IStatement}
267     function GetSQLParams: ISQLParams; override;
268     function GetMetaData: IMetaData; override;
269 tony 56 function GetPlan: AnsiString;
270 tony 45 function IsPrepared: boolean;
271     function CreateBlob(column: TColumnMetaData): IBlob; override;
272     function CreateArray(column: TColumnMetaData): IArray; override;
273     procedure SetRetainInterfaces(aValue: boolean); override;
274     property Handle: TISC_STMT_HANDLE read FHandle;
275    
276     end;
277    
278     implementation
279    
280 tony 47 uses IBUtils, FBMessages, FBBlob, FB25Blob, variants, IBErrorCodes, FBArray, FB25Array
281     {$IFDEF UNIX}, BaseUnix {$ENDIF};
282 tony 45
283    
284     { TIBXSQLVAR }
285    
286     function TIBXSQLVAR.GetSQLType: cardinal;
287     begin
288     result := FXSQLVAR^.sqltype and (not 1);
289     end;
290    
291     function TIBXSQLVAR.GetSubtype: integer;
292     begin
293     if GetSQLType = SQL_BLOB then
294     result := FXSQLVAR^.sqlsubtype
295     else
296     result := 0;
297     end;
298    
299 tony 56 function TIBXSQLVAR.GetAliasName: AnsiString;
300 tony 45 begin
301     result := strpas(FXSQLVAR^.aliasname);
302     end;
303    
304 tony 56 function TIBXSQLVAR.GetFieldName: AnsiString;
305 tony 45 begin
306     result := strpas(FXSQLVAR^.sqlname);
307     end;
308    
309 tony 56 function TIBXSQLVAR.GetOwnerName: AnsiString;
310 tony 45 begin
311     result := strpas(FXSQLVAR^.ownname);
312     end;
313    
314 tony 56 function TIBXSQLVAR.GetRelationName: AnsiString;
315 tony 45 begin
316     result := strpas(FXSQLVAR^.relname);
317     end;
318    
319     function TIBXSQLVAR.GetScale: integer;
320     begin
321     if GetSQLType = SQL_BLOB then
322     result := 0
323     else
324     result := FXSQLVAR^.sqlscale;
325     end;
326    
327     function TIBXSQLVAR.GetCharSetID: cardinal;
328     begin
329     result := 0;
330     case SQLType of
331     SQL_VARYING, SQL_TEXT:
332     {see http://firebirdsql.org/rlsnotesh/rlsnotes210.html}
333     result := FXSQLVAR^.sqlsubtype and $FF;
334    
335     SQL_BLOB:
336     if (SQLSubType = 1) then
337     {see http://firebirdsql.org/rlsnotesh/rlsnotes210.html}
338 tony 60 result := FXSQLVAR^.sqlscale and $FF;
339 tony 45
340     SQL_ARRAY:
341     if (GetRelationName <> '') and (GetFieldName <> '') then
342     result := GetArrayMetaData.GetCharSetID;
343     end;
344     end;
345    
346     function TIBXSQLVAR.GetCodePage: TSystemCodePage;
347     begin
348     result := CP_NONE;
349 tony 60 with Statement.GetAttachment do
350 tony 45 CharSetID2CodePage(GetCharSetID,result);
351     end;
352    
353     function TIBXSQLVAR.GetIsNull: Boolean;
354     begin
355     result := IsNullable and (FNullIndicator = -1);
356     end;
357    
358     function TIBXSQLVAR.GetIsNullable: boolean;
359     begin
360     result := (FXSQLVAR^.sqltype and 1 = 1);
361     end;
362    
363 tony 56 function TIBXSQLVAR.GetSQLData: PByte;
364 tony 45 begin
365     Result := FXSQLVAR^.sqldata;
366     end;
367    
368     function TIBXSQLVAR.GetDataLength: cardinal;
369     begin
370     Result := FXSQLVAR^.sqllen;
371     end;
372    
373     function TIBXSQLVAR.GetArrayMetaData: IArrayMetaData;
374     begin
375     if GetSQLType <> SQL_ARRAY then
376     IBError(ibxeInvalidDataConversion,[nil]);
377    
378     if FArrayMetaData = nil then
379     FArrayMetaData := TFB25ArrayMetaData.Create(FStatement.GetAttachment as TFB25Attachment,
380     FStatement.GetTransaction as TFB25Transaction,
381     GetRelationName,GetFieldName);
382     Result := FArrayMetaData;
383     end;
384    
385     function TIBXSQLVAR.GetBlobMetaData: IBlobMetaData;
386     begin
387     if GetSQLType <> SQL_BLOB then
388     IBError(ibxeInvalidDataConversion,[nil]);
389    
390     if FBlobMetaData = nil then
391     FBlobMetaData := TFB25BlobMetaData.Create(FStatement.GetAttachment as TFB25Attachment,
392     FStatement.GetTransaction as TFB25Transaction,
393     GetRelationName,GetFieldName,GetSubType);
394 tony 47 (FBlobMetaData as TFBBlobMetaData).SetCharSetID(GetCharSetID);
395 tony 45 Result := FBlobMetaData;
396     end;
397    
398     function TIBXSQLVAR.GetAsArray(Array_ID: TISC_QUAD): IArray;
399     begin
400     if SQLType <> SQL_ARRAY then
401     IBError(ibxeInvalidDataConversion,[nil]);
402    
403     if IsNull then
404     Result := nil
405     else
406     begin
407     if FArray = nil then
408     FArray := TFB25Array.Create(FStatement.GetAttachment as TFB25Attachment,
409     TIBXSQLDA(Parent).GetTransaction,
410     GetArrayMetaData,Array_ID);
411     Result := FArray;
412     end;
413     end;
414    
415     function TIBXSQLVAR.GetAsBlob(Blob_ID: TISC_QUAD; BPB: IBPB): IBlob;
416     begin
417     if FBlob <> nil then
418     Result := FBlob
419     else
420     begin
421     if SQLType <> SQL_BLOB then
422     IBError(ibxeInvalidDataConversion, [nil]);
423     if IsNull then
424     Result := nil
425     else
426     Result := TFB25Blob.Create(FStatement.GetAttachment as TFB25Attachment,
427     TIBXSQLDA(Parent).GetTransaction,
428     GetBlobMetaData,
429     Blob_ID,BPB);
430     FBlob := Result;
431     end;
432     end;
433    
434     function TIBXSQLVAR.CreateBlob: IBlob;
435     begin
436     Result := TFB25Blob.Create(FStatement.GetAttachment as TFB25Attachment,
437     FStatement.GetTransaction as TFB25Transaction,GetSubType,GetCharSetID,nil);
438     end;
439    
440     procedure TIBXSQLVAR.Initialize;
441     begin
442     inherited Initialize;
443     FOwnsSQLData := true;
444     with FirebirdClientAPI, FXSQLVar^ do
445     begin
446     case sqltype and (not 1) of
447     SQL_TEXT, SQL_TYPE_DATE, SQL_TYPE_TIME, SQL_TIMESTAMP,
448     SQL_BLOB, SQL_ARRAY, SQL_QUAD, SQL_SHORT, SQL_BOOLEAN,
449     SQL_LONG, SQL_INT64, SQL_DOUBLE, SQL_FLOAT, SQL_D_FLOAT: begin
450     if (sqllen = 0) then
451     { Make sure you get a valid pointer anyway
452     select '' from foo }
453     IBAlloc(sqldata, 0, 1)
454     else
455     IBAlloc(sqldata, 0, sqllen)
456     end;
457     SQL_VARYING: begin
458     IBAlloc(sqldata, 0, sqllen + 2);
459     end;
460     else
461     IBError(ibxeUnknownSQLDataType, [sqltype and (not 1)])
462     end;
463     if (sqltype and 1 = 1) then
464     begin
465     sqlInd := @FNullIndicator;
466     FNullIndicator := -1;
467     end
468     else
469     sqlInd := nil;
470     end;
471     end;
472    
473     procedure TIBXSQLVAR.SetIsNull(Value: Boolean);
474     begin
475     if Value then
476     begin
477 tony 47 IsNullable := true;
478     FNullIndicator := -1;
479 tony 45 Changed;
480     end
481     else
482     if ((not Value) and IsNullable) then
483     begin
484     FNullIndicator := 0;
485     Changed;
486     end;
487     end;
488    
489     procedure TIBXSQLVAR.SetIsNullable(Value: Boolean);
490     begin
491     if (Value <> IsNullable) then
492     begin
493     if Value then
494     begin
495     FXSQLVAR^.sqltype := FXSQLVAR^.sqltype or 1;
496     FNullIndicator := 0;
497     FXSQLVAR^.sqlInd := @FNullIndicator;
498     end
499     else
500     begin
501     FXSQLVAR^.sqltype := FXSQLVAR^.sqltype and (not 1);
502     FXSQLVAR^.sqlind := nil;
503     end;
504     end;
505     end;
506    
507 tony 56 procedure TIBXSQLVAR.SetSQLData(AValue: PByte; len: cardinal);
508 tony 45 begin
509     if FOwnsSQLData then
510     FreeMem(FXSQLVAR^.sqldata);
511     FXSQLVAR^.sqldata := AValue;
512     FXSQLVAR^.sqllen := len;
513     FOwnsSQLData := false;
514     end;
515    
516     procedure TIBXSQLVAR.SetScale(aValue: integer);
517     begin
518     FXSQLVAR^.sqlscale := aValue;
519     end;
520    
521     procedure TIBXSQLVAR.SetDataLength(len: cardinal);
522     begin
523     if not FOwnsSQLData then
524     FXSQLVAR^.sqldata := nil;
525     FXSQLVAR^.sqllen := len;
526     with FirebirdClientAPI do
527     IBAlloc(FXSQLVAR^.sqldata, 0, FXSQLVAR^.sqllen);
528     FOwnsSQLData := true;
529     end;
530    
531     procedure TIBXSQLVAR.SetSQLType(aValue: cardinal);
532     begin
533     FXSQLVAR^.sqltype := aValue or (FXSQLVAR^.sqltype and 1);
534     end;
535    
536     procedure TIBXSQLVAR.SetCharSetID(aValue: cardinal);
537     begin
538     if aValue <> GetCharSetID then
539     case SQLType of
540     SQL_VARYING, SQL_TEXT:
541     FXSQLVAR^.sqlsubtype := (aValue and $FF) or (FXSQLVAR^.sqlsubtype and not $FF);
542    
543     SQL_BLOB,
544     SQL_ARRAY:
545     IBError(ibxeInvalidDataConversion,[nil]);
546     end;
547     end;
548    
549     constructor TIBXSQLVAR.Create(aParent: TIBXSQLDA; aIndex: integer);
550     begin
551     inherited Create(aParent,aIndex);
552     FStatement := aParent.Statement;
553     end;
554    
555     procedure TIBXSQLVAR.FreeSQLData;
556     begin
557     if FOwnsSQLData then
558     FreeMem(FXSQLVAR^.sqldata);
559     FXSQLVAR^.sqldata := nil;
560     FOwnsSQLData := true;
561     end;
562    
563     procedure TIBXSQLVAR.RowChange;
564     begin
565     inherited RowChange;
566     FBlob := nil;
567     FArray := nil;
568     end;
569    
570    
571     { TResultSet }
572    
573     constructor TResultSet.Create(aResults: TIBXOUTPUTSQLDA);
574     begin
575     inherited Create(aResults);
576     FResults := aResults;
577     FCursorSeqNo := aResults.FStatement.FCursorSeqNo;
578     end;
579    
580     destructor TResultSet.Destroy;
581     begin
582     Close;
583     inherited Destroy;
584     end;
585    
586     function TResultSet.FetchNext: boolean;
587     var i: integer;
588     begin
589     CheckActive;
590     Result := FResults.FStatement.FetchNext;
591     if Result then
592     for i := 0 to getCount - 1 do
593     FResults.Column[i].RowChange;
594     end;
595    
596 tony 56 function TResultSet.GetCursorName: AnsiString;
597 tony 45 begin
598     Result := FResults.FStatement.FCursor;
599     end;
600    
601     function TResultSet.GetTransaction: ITransaction;
602     begin
603     Result := FResults.GetTransaction;
604     end;
605    
606     function TResultSet.IsEof: boolean;
607     begin
608     Result := FResults.FStatement.FEof;
609     end;
610    
611     procedure TResultSet.Close;
612     begin
613     if FCursorSeqNo = FResults.FStatement.FCursorSeqNo then
614     FResults.FStatement.Close;
615     end;
616    
617     { TIBXINPUTSQLDA }
618    
619     procedure TIBXINPUTSQLDA.Bind;
620     begin
621     if Count = 0 then
622     Count := 1;
623     with Firebird25ClientAPI do
624     begin
625     if (FXSQLDA <> nil) then
626     if isc_dsql_describe_bind(StatusVector, @(FStatement.Handle), FStatement.SQLDialect,
627     FXSQLDA) > 0 then
628     IBDataBaseError;
629    
630     if FXSQLDA^.sqld > FXSQLDA^.sqln then
631     begin
632     Count := FXSQLDA^.sqld;
633     if isc_dsql_describe_bind(StatusVector, @(FStatement.Handle), FStatement.SQLDialect,
634     FXSQLDA) > 0 then
635     IBDataBaseError;
636     end
637     else
638     if FXSQLDA^.sqld = 0 then
639     Count := 0;
640     end;
641     Initialize;
642     end;
643    
644     function TIBXINPUTSQLDA.IsInputDataArea: boolean;
645     begin
646     Result := true;
647     end;
648    
649     { TIBXOUTPUTSQLDA }
650    
651     procedure TIBXOUTPUTSQLDA.Bind;
652     begin
653     { Allocate an initial output descriptor (with one column) }
654     Count := 1;
655     with Firebird25ClientAPI do
656     begin
657     { Using isc_dsql_describe, get the right size for the columns... }
658     if isc_dsql_describe(StatusVector, @(FStatement.Handle), FStatement.SQLDialect, FXSQLDA) > 0 then
659     IBDataBaseError;
660    
661     if FXSQLDA^.sqld > FXSQLDA^.sqln then
662     begin
663     Count := FXSQLDA^.sqld;
664     if isc_dsql_describe(StatusVector, @(FStatement.Handle), FStatement.SQLDialect, FXSQLDA) > 0 then
665     IBDataBaseError;
666     end
667     else
668     if FXSQLDA^.sqld = 0 then
669     Count := 0;
670     end;
671     Initialize;
672     SetUniqueRelationName;
673     end;
674    
675     function TIBXOUTPUTSQLDA.GetTransaction: TFB25Transaction;
676     begin
677     Result := FTransaction;
678     end;
679    
680     procedure TIBXOUTPUTSQLDA.GetData(index: integer; var aIsNull:boolean; var len: short;
681 tony 56 var data: PByte);
682 tony 45 begin
683     with TIBXSQLVAR(Column[index]), FXSQLVAR^ do
684     begin
685     aIsNull := (sqltype and 1 = 1) and (FNullIndicator = -1);
686     data := sqldata;
687     len := sqllen;
688     if not IsNull and ((sqltype and (not 1)) = SQL_VARYING) then
689     begin
690     with FirebirdClientAPI do
691     len := DecodeInteger(data,2);
692     Inc(data,2);
693     end;
694     end;
695     end;
696    
697     function TIBXOUTPUTSQLDA.IsInputDataArea: boolean;
698     begin
699     Result := false;
700     end;
701    
702     { TIBXSQLDA }
703     constructor TIBXSQLDA.Create(aStatement: TFB25Statement);
704     begin
705     inherited Create;
706     FStatement := aStatement;
707     FSize := 0;
708     // writeln('Creating ',ClassName);
709     end;
710    
711     destructor TIBXSQLDA.Destroy;
712     begin
713     FreeXSQLDA;
714     // writeln('Destroying ',ClassName);
715     inherited Destroy;
716     end;
717    
718     function TIBXSQLDA.CheckStatementStatus(Request: TStatementStatus): boolean;
719     begin
720     Result := false;
721     case Request of
722     ssPrepared:
723     Result := FStatement.IsPrepared;
724    
725     ssExecuteResults:
726     Result :=FStatement.FSingleResults;
727    
728     ssCursorOpen:
729     Result := FStatement.FOpen;
730    
731     ssBOF:
732     Result := FStatement.FBOF;
733    
734     ssEOF:
735     Result := FStatement.FEOF;
736     end;
737     end;
738    
739     function TIBXSQLDA.ColumnsInUseCount: integer;
740     begin
741     Result := FCount;
742     end;
743    
744     function TIBXSQLDA.GetRecordSize: Integer;
745     begin
746     result := SizeOf(TIBXSQLDA) + XSQLDA_LENGTH(FSize);
747     end;
748    
749     function TIBXSQLDA.GetXSQLDA: PXSQLDA;
750     begin
751     result := FXSQLDA;
752     end;
753    
754     function TIBXSQLDA.GetTransactionSeqNo: integer;
755     begin
756     Result := FTransactionSeqNo;
757     end;
758    
759     procedure TIBXSQLDA.Initialize;
760     begin
761     if FXSQLDA <> nil then
762     inherited Initialize;
763     end;
764    
765     function TIBXSQLDA.StateChanged(var ChangeSeqNo: integer): boolean;
766     begin
767     Result := FStatement.ChangeSeqNo <> ChangeSeqNo;
768     if Result then
769     ChangeSeqNo := FStatement.ChangeSeqNo;
770     end;
771    
772     function TIBXSQLDA.GetTransaction: TFB25Transaction;
773     begin
774     Result := FStatement.GetTransaction as TFB25Transaction;
775     end;
776    
777     procedure TIBXSQLDA.SetCount(Value: Integer);
778     var
779     i, OldSize: Integer;
780     p : PXSQLVAR;
781     begin
782     FCount := Value;
783     if FCount = 0 then
784     FUniqueRelationName := ''
785     else
786     begin
787     if FSize > 0 then
788     OldSize := XSQLDA_LENGTH(FSize)
789     else
790     OldSize := 0;
791     if Count > FSize then
792     begin
793     Firebird25ClientAPI.IBAlloc(FXSQLDA, OldSize, XSQLDA_LENGTH(Count));
794     SetLength(FColumnList, FCount);
795     FXSQLDA^.version := SQLDA_VERSION1;
796     p := @FXSQLDA^.sqlvar[0];
797     for i := 0 to Count - 1 do
798     begin
799     if i >= FSize then
800     FColumnList[i] := TIBXSQLVAR.Create(self,i);
801     TIBXSQLVAR(Column[i]).FXSQLVAR := p;
802 tony 56 p := Pointer(PAnsiChar(p) + sizeof(FXSQLDA^.sqlvar));
803 tony 45 end;
804     FSize := inherited Count;
805     end;
806     if FSize > 0 then
807     begin
808     FXSQLDA^.sqln := Value;
809     FXSQLDA^.sqld := Value;
810     end;
811     end;
812     end;
813    
814     procedure TIBXSQLDA.FreeXSQLDA;
815     var i: integer;
816     begin
817     if FXSQLDA <> nil then
818     begin
819     // writeln('SQLDA Cleanup');
820     for i := 0 to Count - 1 do
821     TIBXSQLVAR(Column[i]).FreeSQLData;
822     FreeMem(FXSQLDA);
823     FXSQLDA := nil;
824     end;
825     for i := 0 to FSize - 1 do
826     TIBXSQLVAR(Column[i]).Free;
827     SetLength(FColumnList,0);
828     FSize := 0;
829     end;
830    
831     function TIBXSQLDA.GetStatement: IStatement;
832     begin
833     Result := FStatement;
834     end;
835    
836     function TIBXSQLDA.GetPrepareSeqNo: integer;
837     begin
838     Result := FStatement.FPrepareSeqNo;
839     end;
840    
841     { TFB25Statement }
842    
843 tony 47 procedure TFB25Statement.GetPerfCounters(var counters: TPerfStatistics);
844     var DBInfo: IDBInformation;
845     i: integer;
846     {$IFDEF UNIX}
847     times: tms;
848     {$ENDIF}
849     begin
850     {$IFDEF UNIX}
851     FpTimes(times);
852     counters[psUserTime] := times.tms_utime;
853     {$ELSE}
854     counters[psUserTime] := 0;
855     {$ENDIF}
856 tony 56 counters[psRealTime] := TimeStampToMSecs(DateTimeToTimeStamp(Now));
857 tony 47
858     DBInfo := GetAttachment.GetDBInformation([isc_info_reads,isc_info_writes,
859     isc_info_fetches, isc_info_num_buffers, isc_info_current_memory,
860     isc_info_max_memory]);
861     if DBInfo <> nil then
862     begin
863     for i := 0 to DBInfo.Count - 1 do
864     with DBInfo[i] do
865     case getItemType of
866     isc_info_reads:
867     counters[psReads] := AsInteger;
868     isc_info_writes:
869     counters[psWrites] := AsInteger;
870     isc_info_fetches:
871     counters[psFetches] := AsInteger;
872     isc_info_num_buffers:
873     counters[psBuffers] := AsInteger;
874     isc_info_current_memory:
875     counters[psCurrentMemory] := AsInteger;
876     isc_info_max_memory:
877     counters[psMaxMemory] := AsInteger;
878     end;
879     end;
880     end;
881    
882 tony 45 procedure TFB25Statement.CheckHandle;
883     begin
884     if FHandle = nil then
885     IBError(ibxeInvalidStatementHandle,[nil]);
886     end;
887    
888     procedure TFB25Statement.GetDsqlInfo(info_request: byte; buffer: ISQLInfoResults
889     );
890     begin
891     with Firebird25ClientAPI, buffer as TSQLInfoResultsBuffer do
892     if isc_dsql_sql_info(StatusVector, @(FHandle), 1, @info_request,
893     GetBufSize, Buffer) > 0 then
894     IBDatabaseError;
895     end;
896    
897     procedure TFB25Statement.InternalPrepare;
898     var
899     RB: ISQLInfoResults;
900     TRHandle: TISC_TR_HANDLE;
901     begin
902     if FPrepared then
903     Exit;
904     if (FSQL = '') then
905     IBError(ibxeEmptyQuery, [nil]);
906     try
907     CheckTransaction(FTransactionIntf);
908     with Firebird25ClientAPI do
909     begin
910     Call(isc_dsql_alloc_statement2(StatusVector, @(FDBHandle),
911     @FHandle), True);
912     TRHandle := (FTransactionIntf as TFB25Transaction).Handle;
913     if FHasParamNames then
914     begin
915     if FProcessedSQL = '' then
916     FSQLParams.PreprocessSQL(FSQL,FGenerateParamNames,FProcessedSQL);
917     Call(isc_dsql_prepare(StatusVector, @(TRHandle), @FHandle, 0,
918 tony 56 PAnsiChar(FProcessedSQL), FSQLDialect, nil), True);
919 tony 45 end
920     else
921     Call(isc_dsql_prepare(StatusVector, @(TRHandle), @FHandle, 0,
922 tony 56 PAnsiChar(FSQL), FSQLDialect, nil), True);
923 tony 45 end;
924     { After preparing the statement, query the stmt type and possibly
925     create a FSQLRecord "holder" }
926     { Get the type of the statement }
927     RB := GetDsqlInfo(isc_info_sql_stmt_type);
928     if RB.Count > 0 then
929     FSQLStatementType := TIBSQLStatementTypes(RB[0].GetAsInteger)
930     else
931     FSQLStatementType := SQLUnknown;
932    
933     case FSQLStatementType of
934     SQLGetSegment,
935     SQLPutSegment,
936     SQLStartTransaction: begin
937     FreeHandle;
938     IBError(ibxeNotPermitted, [nil]);
939     end;
940     SQLCommit,
941     SQLRollback,
942     SQLDDL, SQLSetGenerator,
943     SQLInsert, SQLUpdate, SQLDelete, SQLSelect, SQLSelectForUpdate,
944     SQLExecProcedure:
945     begin
946     {set up input sqlda}
947     FSQLParams.Bind;
948    
949     {setup output sqlda}
950     if FSQLStatementType in [SQLSelect, SQLSelectForUpdate,
951     SQLExecProcedure] then
952     FSQLRecord.Bind;
953     end;
954     end;
955     except
956     on E: Exception do begin
957     if (FHandle <> nil) then
958     FreeHandle;
959     if E is EIBInterBaseError then
960     raise EIBInterBaseError.Create(EIBInterBaseError(E).SQLCode,
961     EIBInterBaseError(E).IBErrorCode,
962     EIBInterBaseError(E).Message +
963     sSQLErrorSeparator + FSQL)
964     else
965     raise;
966     end;
967     end;
968     FPrepared := true;
969     FSingleResults := false;
970     if RetainInterfaces then
971     begin
972     SetRetainInterfaces(false);
973     SetRetainInterfaces(true);
974     end;
975     Inc(FPrepareSeqNo);
976     Inc(FChangeSeqNo);
977     with FTransactionIntf as TFB25Transaction do
978     begin
979     FSQLParams.FTransactionSeqNo := TransactionSeqNo;
980     FSQLRecord.FTransactionSeqNo := TransactionSeqNo;
981     end;
982     end;
983    
984     function TFB25Statement.InternalExecute(aTransaction: ITransaction): IResults;
985     var TRHandle: TISC_TR_HANDLE;
986     begin
987     Result := nil;
988     FBOF := false;
989     FEOF := false;
990     FSingleResults := false;
991     CheckTransaction(aTransaction);
992     if not FPrepared then
993     InternalPrepare;
994     CheckHandle;
995     if aTransaction <> FTransactionIntf then
996     AddMonitor(aTransaction as TFB25Transaction);
997     if (FSQLParams.FTransactionSeqNo < (FTransactionIntf as TFB25transaction).TransactionSeqNo) then
998     IBError(ibxeInterfaceOutofDate,[nil]);
999    
1000     try
1001     TRHandle := (aTransaction as TFB25Transaction).Handle;
1002     with Firebird25ClientAPI do
1003     begin
1004 tony 47 if FCollectStatistics then
1005     GetPerfCounters(FBeforeStats);
1006 tony 45
1007 tony 47 case FSQLStatementType of
1008     SQLSelect:
1009     IBError(ibxeIsAExecuteProcedure,[]);
1010    
1011     SQLExecProcedure:
1012     begin
1013     Call(isc_dsql_execute2(StatusVector,
1014     @(TRHandle),
1015     @FHandle,
1016     SQLDialect,
1017     FSQLParams.AsXSQLDA,
1018     FSQLRecord.AsXSQLDA), True);
1019     Result := TResults.Create(FSQLRecord);
1020     FSingleResults := true;
1021     end
1022     else
1023     Call(isc_dsql_execute(StatusVector,
1024     @(TRHandle),
1025     @FHandle,
1026     SQLDialect,
1027     FSQLParams.AsXSQLDA), True);
1028    
1029     end;
1030     if FCollectStatistics then
1031     begin
1032     GetPerfCounters(FAfterStats);
1033     FStatisticsAvailable := true;
1034     end;
1035 tony 45 end;
1036     finally
1037     if aTransaction <> FTransactionIntf then
1038     RemoveMonitor(aTransaction as TFB25Transaction);
1039     end;
1040     FExecTransactionIntf := aTransaction;
1041     Inc(FChangeSeqNo);
1042     end;
1043    
1044     function TFB25Statement.InternalOpenCursor(aTransaction: ITransaction
1045     ): IResultSet;
1046     var TRHandle: TISC_TR_HANDLE;
1047     GUID : TGUID;
1048     begin
1049     if FSQLStatementType <> SQLSelect then
1050     IBError(ibxeIsASelectStatement,[]);
1051    
1052     CheckTransaction(aTransaction);
1053     if not FPrepared then
1054     InternalPrepare;
1055     CheckHandle;
1056     if aTransaction <> FTransactionIntf then
1057     AddMonitor(aTransaction as TFB25Transaction);
1058     if (FSQLParams.FTransactionSeqNo < (FTransactionIntf as TFB25transaction).TransactionSeqNo) then
1059     IBError(ibxeInterfaceOutofDate,[nil]);
1060    
1061     with Firebird25ClientAPI do
1062     begin
1063 tony 47 if FCollectStatistics then
1064     GetPerfCounters(FBeforeStats);
1065    
1066 tony 45 TRHandle := (aTransaction as TFB25Transaction).Handle;
1067     Call(isc_dsql_execute2(StatusVector,
1068     @(TRHandle),
1069     @FHandle,
1070     SQLDialect,
1071     FSQLParams.AsXSQLDA,
1072     nil), True);
1073     if FCursor = '' then
1074     begin
1075     CreateGuid(GUID);
1076     FCursor := GUIDToString(GUID);
1077     Call(
1078 tony 56 isc_dsql_set_cursor_name(StatusVector, @FHandle, PAnsiChar(FCursor), 0),
1079 tony 45 True);
1080     end;
1081 tony 47
1082     if FCollectStatistics then
1083     begin
1084     GetPerfCounters(FAfterStats);
1085     FStatisticsAvailable := true;
1086     end;
1087 tony 45 end;
1088     Inc(FCursorSeqNo);
1089     FSingleResults := false;
1090     FOpen := True;
1091     FExecTransactionIntf := aTransaction;
1092     FBOF := true;
1093     FEOF := false;
1094     FSQLRecord.FTransaction := aTransaction as TFB25Transaction;
1095     FSQLRecord.FTransactionSeqNo := FSQLRecord.FTransaction.TransactionSeqNo;
1096     Result := TResultSet.Create(FSQLRecord);
1097     Inc(FChangeSeqNo);
1098     end;
1099    
1100     procedure TFB25Statement.FreeHandle;
1101     var
1102     isc_res: ISC_STATUS;
1103     begin
1104     Close;
1105     ReleaseInterfaces;
1106     try
1107     if FHandle <> nil then
1108     with Firebird25ClientAPI do
1109     begin
1110     isc_res :=
1111     Call(isc_dsql_free_statement(StatusVector, @FHandle, DSQL_drop), False);
1112     if (StatusVector^ = 1) and (isc_res > 0) and (isc_res <> isc_bad_stmt_handle) then
1113     IBDataBaseError;
1114     end;
1115     finally
1116     FHandle := nil;
1117     FCursor := '';
1118     FPrepared := false;
1119     end;
1120     end;
1121    
1122     procedure TFB25Statement.InternalClose(Force: boolean);
1123     var
1124     isc_res: ISC_STATUS;
1125     begin
1126     if (FHandle <> nil) and (SQLStatementType = SQLSelect) and FOpen then
1127     try
1128     with Firebird25ClientAPI do
1129     begin
1130     isc_res := Call(
1131     isc_dsql_free_statement(StatusVector, @FHandle, DSQL_close),
1132     False);
1133     if not Force and (StatusVector^ = 1) and (isc_res > 0) and
1134     not getStatus.CheckStatusVector(
1135     [isc_bad_stmt_handle, isc_dsql_cursor_close_err]) then
1136     IBDatabaseError;
1137     end;
1138     finally
1139 tony 56 if (FSQLRecord.FTransaction <> nil) and (FSQLRecord.FTransaction <> (FTransactionIntf as TFB25Transaction)) then
1140 tony 45 RemoveMonitor(FSQLRecord.FTransaction);
1141     FOpen := False;
1142     FExecTransactionIntf := nil;
1143     FSQLRecord.FTransaction := nil;
1144     Inc(FChangeSeqNo);
1145     end;
1146     end;
1147    
1148     constructor TFB25Statement.Create(Attachment: TFB25Attachment;
1149 tony 56 Transaction: ITransaction; sql: AnsiString; aSQLDialect: integer);
1150 tony 45 begin
1151     inherited Create(Attachment,Transaction,sql,aSQLDialect);
1152     FDBHandle := Attachment.Handle;
1153     FSQLParams := TIBXINPUTSQLDA.Create(self);
1154     FSQLRecord := TIBXOUTPUTSQLDA.Create(self);
1155     InternalPrepare;
1156     end;
1157    
1158     constructor TFB25Statement.CreateWithParameterNames(Attachment: TFB25Attachment;
1159 tony 56 Transaction: ITransaction; sql: AnsiString; aSQLDialect: integer;
1160 tony 45 GenerateParamNames: boolean);
1161     begin
1162     inherited CreateWithParameterNames(Attachment,Transaction,sql,aSQLDialect,GenerateParamNames);
1163     FDBHandle := Attachment.Handle;
1164     FSQLParams := TIBXINPUTSQLDA.Create(self);
1165     FSQLRecord := TIBXOUTPUTSQLDA.Create(self);
1166     InternalPrepare;
1167     end;
1168    
1169     destructor TFB25Statement.Destroy;
1170     begin
1171     inherited Destroy;
1172     if assigned(FSQLParams) then FSQLParams.Free;
1173     if assigned(FSQLRecord) then FSQLRecord.Free;
1174     end;
1175    
1176     function TFB25Statement.FetchNext: boolean;
1177     var
1178     fetch_res: ISC_STATUS;
1179     begin
1180     result := false;
1181     if not FOpen then
1182     IBError(ibxeSQLClosed, [nil]);
1183     if FEOF then
1184     IBError(ibxeEOF,[nil]);
1185    
1186     with Firebird25ClientAPI do
1187     begin
1188     { Go to the next record... }
1189     fetch_res :=
1190     Call(isc_dsql_fetch(StatusVector, @FHandle, SQLDialect, FSQLRecord.AsXSQLDA), False);
1191     if (fetch_res = 100) or (getStatus.CheckStatusVector([isc_dsql_cursor_err])) then
1192     begin
1193     FBOF := false;
1194     FEOF := true;
1195     Exit; {End of File}
1196     end
1197     else
1198     if (fetch_res > 0) then
1199     begin
1200     try
1201     IBDataBaseError;
1202     except
1203     Close;
1204     raise;
1205     end;
1206     end
1207     else
1208     begin
1209     FBOF := false;
1210     result := true;
1211     end;
1212     end;
1213     FSQLRecord.RowChange;
1214     if FEOF then
1215     Inc(FChangeSeqNo);
1216     end;
1217    
1218     function TFB25Statement.GetSQLParams: ISQLParams;
1219     begin
1220     CheckHandle;
1221     if not HasInterface(0) then
1222     AddInterface(0,TSQLParams.Create(FSQLParams));
1223     Result := TSQLParams(GetInterface(0));
1224     end;
1225    
1226     function TFB25Statement.GetMetaData: IMetaData;
1227     begin
1228     CheckHandle;
1229     if not HasInterface(1) then
1230     AddInterface(1, TMetaData.Create(FSQLRecord));
1231     Result := TMetaData(GetInterface(1));
1232     end;
1233    
1234 tony 56 function TFB25Statement.GetPlan: AnsiString;
1235 tony 45 var
1236     RB: ISQLInfoResults;
1237     begin
1238     if (not (FSQLStatementType in [SQLSelect, SQLSelectForUpdate,
1239     {TODO: SQLExecProcedure, }
1240     SQLUpdate, SQLDelete])) then
1241     result := ''
1242     else
1243     begin
1244     RB := TSQLInfoResultsBuffer.Create(4*4096);
1245     GetDsqlInfo(isc_info_sql_get_plan,RB);
1246     if RB.Count > 0 then
1247     Result := RB[0].GetAsString;
1248     end;
1249     end;
1250    
1251     function TFB25Statement.CreateBlob(column: TColumnMetaData): IBlob;
1252     begin
1253     if assigned(column) and (column.SQLType <> SQL_Blob) then
1254     IBError(ibxeNotABlob,[nil]);
1255     Result := TFB25Blob.Create(GetAttachment as TFB25Attachment,FExecTransactionIntf as TFB25Transaction,
1256     column.GetBlobMetaData,nil);
1257     end;
1258    
1259     function TFB25Statement.CreateArray(column: TColumnMetaData): IArray;
1260     begin
1261     if assigned(column) and (column.SQLType <> SQL_ARRAY) then
1262     IBError(ibxeNotAnArray,[nil]);
1263     Result := TFB25Array.Create(GetAttachment as TFB25Attachment,FExecTransactionIntf as TFB25Transaction,
1264     column.GetArrayMetaData);
1265     end;
1266    
1267     procedure TFB25Statement.SetRetainInterfaces(aValue: boolean);
1268     begin
1269     inherited SetRetainInterfaces(aValue);
1270     if HasInterface(1) then
1271     TMetaData(GetInterface(1)).RetainInterfaces := aValue;
1272     if HasInterface(0) then
1273     TSQLParams(GetInterface(0)).RetainInterfaces := aValue;
1274     end;
1275    
1276     function TFB25Statement.IsPrepared: boolean;
1277     begin
1278     Result := FHandle <> nil;
1279     end;
1280    
1281     end.
1282