ViewVC Help
View File | Revision Log | Show Annotations | Download File | View Changeset | Root Listing
root/public/ibx/trunk/fbintf/client/3.0/FB30Statement.pas
Revision: 56
Committed: Mon Mar 6 10:20:02 2017 UTC (7 years, 1 month ago) by tony
Content type: text/x-pascal
File size: 37591 byte(s)
Log Message:
Committing updates for Trunk

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