ViewVC Help
View File | Revision Log | Show Annotations | Download File | View Changeset | Root Listing
root/public/ibx/branches/udr/client/IBUtils.pas
Revision: 363
Committed: Tue Dec 7 13:30:05 2021 UTC (2 years, 11 months ago) by tony
Content type: text/x-pascal
Original Path: ibx/branches/journaling/fbintf/IBUtils.pas
File size: 75437 byte(s)
Log Message:
add fbintf

File Contents

# User Rev Content
1 tony 45 {************************************************************************}
2     { }
3     { Borland Delphi Visual Component Library }
4     { InterBase Express core components }
5     { }
6     { Copyright (c) 1998-2000 Inprise Corporation }
7     { }
8     { InterBase Express is based in part on the product }
9     { Free IB Components, written by Gregory H. Deatz for }
10     { Hoagland, Longo, Moran, Dunst & Doukas Company. }
11     { Free IB Components is used under license. }
12     { }
13     { The contents of this file are subject to the InterBase }
14     { Public License Version 1.0 (the "License"); you may not }
15     { use this file except in compliance with the License. You }
16     { may obtain a copy of the License at http://www.Inprise.com/IPL.html }
17     { Software distributed under the License is distributed on }
18     { an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either }
19     { express or implied. See the License for the specific language }
20     { governing rights and limitations under the License. }
21     { The Original Code was created by InterBase Software Corporation }
22     { and its successors. }
23     { Portions created by Inprise Corporation are Copyright (C) Inprise }
24     { Corporation. All Rights Reserved. }
25     { Contributor(s): Jeff Overcash }
26     { }
27     { IBX For Lazarus (Firebird Express) }
28     { Contributor: Tony Whyman, MWA Software http://www.mwasoftware.co.uk }
29     { Portions created by MWA Software are copyright McCallum Whyman }
30     { Associates Ltd 2011 }
31     { }
32     {************************************************************************}
33    
34     unit IBUtils;
35 tony 56 {$IFDEF MSWINDOWS}
36 tony 118 {$DEFINE WINDOWS}
37 tony 56 {$ENDIF}
38 tony 45
39     {$IFDEF FPC}
40     {$Mode Delphi}
41     {$codepage UTF8}
42     {$ENDIF}
43    
44 tony 315 { $IF declared(CompilerVersion) and (CompilerVersion >= 22)}
45     { $define HASDELPHIREQEX}
46     { $IFEND}
47 tony 118
48 tony 45 interface
49    
50 tony 143 uses Classes, SysUtils, IB;
51 tony 45
52 tony 363 {$IF not defined(LineEnding)}
53     const
54     {$IFDEF WINDOWS}
55     LineEnding = #$0D#$0A;
56     {$ELSE}
57     LineEnding = #$0A;
58     {$ENDIF}
59     {$IFEND}
60    
61 tony 263 type
62     TSQLTokens = (
63    
64     {Reserved Words}
65    
66     sqltAdd,
67     sqltAdmin,
68     sqltAll,
69     sqltAlter,
70     sqltAnd,
71     sqltAny,
72     sqltAs,
73     sqltAt,
74     sqltAvg,
75     sqltBegin,
76     sqltBetween,
77     sqltBigint,
78     sqltBit_Length,
79     sqltBlob,
80     sqltBoolean,
81     sqltBoth,
82     sqltBy,
83     sqltCase,
84     sqltCast,
85     sqltChar,
86     sqltChar_Length,
87     sqltCharacter,
88     sqltCharacter_Length,
89     sqltCheck,
90     sqltClose,
91     sqltCollate,
92     sqltColumn,
93     sqltCommit,
94     sqltConnect,
95     sqltConstraint,
96     sqltCorr,
97     sqltCount,
98     sqltCovar_Pop,
99     sqltCovar_Samp,
100     sqltCreate,
101     sqltCross,
102     sqltCurrent,
103     sqltCurrent_Connection,
104     sqltCurrent_Date,
105     sqltCurrent_Role,
106     sqltCurrent_Time,
107     sqltCurrent_Timestamp,
108     sqltCurrent_Transaction,
109     sqltCurrent_User,
110     sqltCursor,
111     sqltDate,
112     sqltDay,
113     sqltDec,
114     sqltDecimal,
115     sqltDeclare,
116     sqltDefault,
117     sqltDelete,
118     sqltDeleting,
119     sqltDeterministic,
120     sqltDisconnect,
121     sqltDistinct,
122     sqltDouble,
123     sqltDrop,
124     sqltElse,
125     sqltEnd,
126     sqltEscape,
127     sqltExecute,
128     sqltExists,
129     sqltExternal,
130     sqltExtract,
131     sqltFalse,
132     sqltFetch,
133     sqltFilter,
134     sqltFloat,
135     sqltFor,
136     sqltForeign,
137     sqltFrom,
138     sqltFull,
139     sqltFunction,
140     sqltGdscode,
141     sqltGlobal,
142     sqltGrant,
143     sqltGroup,
144     sqltHaving,
145     sqltHour,
146     sqltIn,
147     sqltIndex,
148     sqltInner,
149     sqltInsensitive,
150     sqltInsert,
151     sqltInserting,
152     sqltInt,
153     sqltInteger,
154     sqltInto,
155     sqltIs,
156     sqltJoin,
157     sqltKey,
158     sqltLeading,
159     sqltLeft,
160     sqltLike,
161     sqltLong,
162     sqltLower,
163     sqltMax,
164     sqltMaximum_Segment,
165     sqltMerge,
166     sqltMin,
167     sqltMinute,
168     sqltMonth,
169     sqltNational,
170     sqltNatural,
171     sqltNchar,
172     sqltNo,
173     sqltNot,
174     sqltNull,
175     sqltNumeric,
176     sqltOctet_Length,
177     sqltOf,
178     sqltOffset,
179     sqltOn,
180     sqltOnly,
181     sqltOpen,
182     sqltOr,
183     sqltOrder,
184     sqltOuter,
185     sqltOver,
186     sqltParameter,
187     sqltPlan,
188     sqltPosition,
189     sqltPost_Event,
190     sqltPrecision,
191     sqltPrimary,
192     sqltProcedure,
193     sqltRdbDb_Key,
194     sqltRdbRecord_Version,
195     sqltReal,
196     sqltRecord_Version,
197     sqltRecreate,
198     sqltRecursive,
199     sqltReferences,
200     sqltRegr_Avgx,
201     sqltRegr_Avgy,
202     sqltRegr_Count,
203     sqltRegr_Intercept,
204     sqltRegr_R2,
205     sqltRegr_Slope,
206     sqltRegr_Sxx,
207     sqltRegr_Sxy,
208     sqltRegr_Syy,
209     sqltRelease,
210     sqltReturn,
211     sqltReturning_Values,
212     sqltReturns,
213     sqltRevoke,
214     sqltRight,
215     sqltRollback,
216     sqltRow,
217     sqltRows,
218     sqltRow_Count,
219     sqltSavepoint,
220     sqltScroll,
221     sqltSecond,
222     sqltSelect,
223     sqltSensitive,
224     sqltSet,
225     sqltSimilar,
226     sqltSmallint,
227     sqltSome,
228     sqltSqlcode,
229     sqltSqlstate,
230     sqltStart,
231     sqltStddev_Pop,
232     sqltStddev_Samp,
233     sqltSum,
234     sqltTable,
235     sqltThen,
236     sqltTime,
237     sqltTimestamp,
238     sqltTo,
239     sqltTrailing,
240     sqltTrigger,
241     sqltTrim,
242     sqltTrue,
243     sqltUnion,
244     sqltUnique,
245     sqltUnknown,
246     sqltUpdate,
247     sqltUpdating,
248     sqltUpper,
249     sqltUser,
250     sqltUsing,
251     sqltValue,
252     sqltValues,
253     sqltVar_Pop,
254     sqltVar_Samp,
255     sqltVarchar,
256     sqltVariable,
257     sqltVarying,
258     sqltView,
259     sqltWhen,
260     sqltWhere,
261     sqltWhile,
262     sqltWith,
263     sqltYear,
264    
265     {symbols}
266    
267     sqltSpace,
268     sqltSemiColon,
269     sqltPlaceholder,
270     sqltSingleQuotes,
271     sqltDoubleQuotes,
272 tony 270 sqltBackslash,
273 tony 263 sqltComma,
274     sqltPeriod,
275     sqltEquals,
276     sqltOtherCharacter,
277     sqltIdentifier,
278     sqltIdentifierInDoubleQuotes,
279     sqltNumberString,
280     sqltString,
281     sqltParam,
282     sqltQuotedParam,
283     sqltColon,
284     sqltComment,
285     sqltCommentLine,
286     sqltQuotedString,
287     sqltAsterisk,
288     sqltForwardSlash,
289     sqltOpenSquareBracket,
290     sqltCloseSquareBracket,
291     sqltOpenBracket,
292     sqltCloseBracket,
293     sqltPipe,
294 tony 287 sqltMinus,
295 tony 263 sqltConcatSymbol,
296     sqltLT,
297     sqltGT,
298     sqltCR,
299     sqltEOL,
300     sqltEOF,
301     sqltInit
302     );
303    
304     TSQLReservedWords = sqltAdd..sqltYear;
305    
306 tony 45 const
307     CRLF = #13 + #10;
308     CR = #13;
309     LF = #10;
310     TAB = #9;
311     NULL_TERMINATOR = #0;
312    
313 tony 263 {SQL Reserved words in alphabetical order}
314    
315     sqlReservedWords: array [TSQLReservedWords] of string = (
316 tony 47 'ADD',
317     'ADMIN',
318     'ALL',
319     'ALTER',
320     'AND',
321     'ANY',
322     'AS',
323     'AT',
324     'AVG',
325     'BEGIN',
326     'BETWEEN',
327     'BIGINT',
328     'BIT_LENGTH',
329     'BLOB',
330     'BOOLEAN',
331     'BOTH',
332     'BY',
333     'CASE',
334     'CAST',
335     'CHAR',
336     'CHAR_LENGTH',
337     'CHARACTER',
338     'CHARACTER_LENGTH',
339     'CHECK',
340     'CLOSE',
341     'COLLATE',
342     'COLUMN',
343     'COMMIT',
344     'CONNECT',
345     'CONSTRAINT',
346     'CORR',
347     'COUNT',
348     'COVAR_POP',
349     'COVAR_SAMP',
350     'CREATE',
351     'CROSS',
352     'CURRENT',
353     'CURRENT_CONNECTION',
354     'CURRENT_DATE',
355     'CURRENT_ROLE',
356     'CURRENT_TIME',
357     'CURRENT_TIMESTAMP',
358     'CURRENT_TRANSACTION',
359     'CURRENT_USER',
360     'CURSOR',
361     'DATE',
362     'DAY',
363     'DEC',
364     'DECIMAL',
365     'DECLARE',
366     'DEFAULT',
367     'DELETE',
368     'DELETING',
369     'DETERMINISTIC',
370     'DISCONNECT',
371     'DISTINCT',
372     'DOUBLE',
373     'DROP',
374     'ELSE',
375     'END',
376     'ESCAPE',
377     'EXECUTE',
378     'EXISTS',
379     'EXTERNAL',
380     'EXTRACT',
381     'FALSE',
382     'FETCH',
383     'FILTER',
384     'FLOAT',
385     'FOR',
386     'FOREIGN',
387     'FROM',
388     'FULL',
389     'FUNCTION',
390     'GDSCODE',
391     'GLOBAL',
392     'GRANT',
393     'GROUP',
394     'HAVING',
395     'HOUR',
396     'IN',
397     'INDEX',
398     'INNER',
399     'INSENSITIVE',
400     'INSERT',
401     'INSERTING',
402     'INT',
403     'INTEGER',
404     'INTO',
405     'IS',
406     'JOIN',
407 tony 209 'KEY',
408 tony 47 'LEADING',
409     'LEFT',
410     'LIKE',
411     'LONG',
412     'LOWER',
413     'MAX',
414     'MAXIMUM_SEGMENT',
415     'MERGE',
416     'MIN',
417     'MINUTE',
418     'MONTH',
419     'NATIONAL',
420     'NATURAL',
421     'NCHAR',
422     'NO',
423     'NOT',
424     'NULL',
425     'NUMERIC',
426     'OCTET_LENGTH',
427     'OF',
428     'OFFSET',
429     'ON',
430     'ONLY',
431     'OPEN',
432     'OR',
433     'ORDER',
434     'OUTER',
435     'OVER',
436     'PARAMETER',
437     'PLAN',
438     'POSITION',
439     'POST_EVENT',
440     'PRECISION',
441     'PRIMARY',
442     'PROCEDURE',
443     'RDB$DB_KEY',
444     'RDB$RECORD_VERSION',
445     'REAL',
446     'RECORD_VERSION',
447     'RECREATE',
448     'RECURSIVE',
449     'REFERENCES',
450     'REGR_AVGX',
451     'REGR_AVGY',
452     'REGR_COUNT',
453     'REGR_INTERCEPT',
454     'REGR_R2',
455     'REGR_SLOPE',
456     'REGR_SXX',
457     'REGR_SXY',
458     'REGR_SYY',
459     'RELEASE',
460     'RETURN',
461     'RETURNING_VALUES',
462     'RETURNS',
463     'REVOKE',
464     'RIGHT',
465     'ROLLBACK',
466     'ROW',
467 tony 263 'ROWS',
468 tony 47 'ROW_COUNT',
469     'SAVEPOINT',
470     'SCROLL',
471     'SECOND',
472     'SELECT',
473     'SENSITIVE',
474     'SET',
475     'SIMILAR',
476     'SMALLINT',
477     'SOME',
478     'SQLCODE',
479     'SQLSTATE',
480     'START',
481     'STDDEV_POP',
482     'STDDEV_SAMP',
483     'SUM',
484     'TABLE',
485     'THEN',
486     'TIME',
487     'TIMESTAMP',
488     'TO',
489     'TRAILING',
490     'TRIGGER',
491     'TRIM',
492     'TRUE',
493     'UNION',
494     'UNIQUE',
495     'UNKNOWN',
496     'UPDATE',
497     'UPDATING',
498     'UPPER',
499     'USER',
500     'USING',
501     'VALUE',
502     'VALUES',
503     'VAR_POP',
504     'VAR_SAMP',
505     'VARCHAR',
506     'VARIABLE',
507     'VARYING',
508     'VIEW',
509     'WHEN',
510     'WHERE',
511     'WHILE',
512     'WITH',
513     'YEAR'
514     );
515 tony 45
516 tony 263 type
517     {The TSQLTokeniser class provides a common means to parse an SQL statement, or
518     even a stream of SQL Statements. The TSQLStringTokeniser class is instantiated
519     with a single SQL statement or a set of concatenated statements. The TSQLStreamTokeniser
520     is instantiated with a stream from which the SQL statements are read.
521    
522     Successive calls to GetNextToken then return each SQL token. The TokenText contains
523     either the single character, the identifier or reserved word, the string or comment.}
524    
525     { TSQLTokeniser }
526    
527     TSQLTokeniser = class
528     private
529     const
530     TokenQueueMaxSize = 64;
531     type
532     TLexState = (stDefault, stInCommentLine, stInComment, stSingleQuoted, stDoubleQuoted,
533     stInIdentifier, stInNumeric);
534    
535     TTokenQueueItem = record
536     token: TSQLTokens;
537     text: AnsiString;
538     end;
539     TTokenQueueState = (tsHold, tsRelease);
540    
541     private
542     FLastChar: AnsiChar;
543     FState: TLexState;
544     FSkipNext: boolean;
545     function GetNext: TSQLTokens;
546    
547     {The token Queue is available for use by descendents so that they can
548     hold back tokens in order to lookahead by token rather than just a single
549     character}
550    
551     private
552     FTokenQueue: array[0..TokenQueueMaxSize] of TTokenQueueItem;
553     FQueueState: TTokenQueueState;
554     FQFirst: integer; {first and last pointers first=last => queue empty}
555     FQLast: integer;
556     FEOF: boolean;
557     procedure PopQueue(var token: TSQLTokens);
558     protected
559     FString: AnsiString;
560     FNextToken: TSQLTokens;
561     procedure Assign(source: TSQLTokeniser); virtual;
562     function GetChar: AnsiChar; virtual; abstract;
563     function TokenFound(var token: TSQLTokens): boolean; virtual;
564     function InternalGetNextToken: TSQLTokens; virtual;
565     procedure Reset; virtual;
566 tony 359 function ReadCharacters(NumOfChars: integer): AnsiString;
567 tony 263
568     {Token stack}
569     procedure QueueToken(token: TSQLTokens; text:AnsiString); overload;
570     procedure QueueToken(token: TSQLTokens); overload;
571     procedure ResetQueue; overload;
572     procedure ResetQueue(token: TSQLTokens; text:AnsiString); overload;
573     procedure ResetQueue(token: TSQLTokens); overload;
574     procedure ReleaseQueue(var token: TSQLTokens); overload;
575     procedure ReleaseQueue; overload;
576     function GetQueuedText: AnsiString;
577     procedure SetTokenText(text: AnsiString);
578    
579     public
580     const
581     DefaultTerminator = ';';
582     public
583     constructor Create;
584     destructor Destroy; override;
585     function GetNextToken: TSQLTokens;
586     property EOF: boolean read FEOF;
587     property TokenText: AnsiString read FString;
588     end;
589    
590     { TSQLwithNamedParamsTokeniser }
591    
592     TSQLwithNamedParamsTokeniser = class(TSQLTokeniser)
593     private
594     type
595     TSQLState = (stInit,stInParam,stInBlock, stInArrayDim);
596     private
597     FState: TSQLState;
598     FNested: integer;
599     protected
600     procedure Assign(source: TSQLTokeniser); override;
601     procedure Reset; override;
602     function TokenFound(var token: TSQLTokens): boolean; override;
603     end;
604    
605 tony 270 { TSQLParamProcessor }
606    
607     TSQLParamProcessor = class(TSQLwithNamedParamsTokeniser)
608     private
609     const
610     sIBXParam = 'IBXParam'; {do not localize}
611     private
612     FInString: AnsiString;
613     FIndex: integer;
614     function DoExecute(GenerateParamNames: boolean;
615     var slNames: TStrings): AnsiString;
616     protected
617     function GetChar: AnsiChar; override;
618     public
619     class function Execute(sSQL: AnsiString; GenerateParamNames: boolean;
620     var slNames: TStrings): AnsiString;
621     end;
622    
623 tony 363 TOnProgressEvent = procedure (Sender: TObject; Reset: boolean; value: integer) of object;
624 tony 270
625 tony 363 { TSQLXMLReader - used to save and read back blob and array data in a pseudo XML format}
626    
627     TSQLXMLReader = class(TSQLTokeniser)
628     private
629     type
630     TXMLStates = (stNoXML, stInTag,stInTagBody,
631     stAttribute,stAttributeValue,stQuotedAttributeValue,
632     stInEndTag, stInEndTagBody,
633     stXMLData);
634    
635     TXMLTag = (xtNone,xtBlob,xtArray,xtElt);
636    
637     TXMLTagDef = record
638     XMLTag: TXMLTag;
639     TagValue: string;
640     end;
641    
642     const
643     XMLTagDefs: array [xtBlob..xtElt] of TXMLTagDef = (
644     (XMLTag: xtBlob; TagValue: 'blob'),
645     (XMLTag: xtArray; TagValue: 'array'),
646     (XMLTag: xtElt; TagValue: 'elt')
647     );
648     MaxXMLTags = 20;
649     BlobLineLength = 40;
650    
651     public
652     const
653     ibx_blob = 'IBX_BLOB';
654     ibx_array = 'IBX_ARRAY';
655    
656     type
657     TBlobData = record
658     BlobIntf: IBlob;
659     SubType: cardinal;
660     end;
661    
662     TArrayData = record
663     ArrayIntf: IArray;
664     SQLType: cardinal;
665     relationName: string;
666     columnName: string;
667     dim: cardinal;
668     Size: cardinal;
669     Scale: integer;
670     CharSet: string;
671     bounds: TArrayBounds;
672     CurrentRow: integer;
673     Index: array of integer;
674     end;
675    
676     private
677     FOnProgressEvent: TOnProgressEvent;
678     FXMLState: TXMLStates;
679     FXMLTagStack: array [1..MaxXMLTags] of TXMLTag;
680     FXMLTagIndex: integer;
681     FAttributeName: string;
682     FBlobData: array of TBlobData;
683     FCurrentBlob: integer;
684     FBlobBuffer: PByte;
685     FArrayData: array of TArrayData;
686     FCurrentArray: integer;
687     FXMLString: string;
688     function FindTag(tag: string; var xmlTag: TXMLTag): boolean;
689     function GetArrayData(index: integer): TArrayData;
690     function GetArrayDataCount: integer;
691     function GetBlobData(index: integer): TBlobData;
692     function GetBlobDataCount: integer;
693     function GetTagName(xmltag: TXMLTag): string;
694     procedure ProcessAttributeValue(attrValue: string);
695     procedure ProcessBoundsList(boundsList: string);
696     procedure ProcessTagValue(tagValue: string);
697     procedure XMLTagInit(xmltag: TXMLTag);
698     function XMLTagEnd(var xmltag: TXMLTag): boolean;
699     procedure XMLTagEnter;
700     protected
701     function GetAttachment: IAttachment; virtual; abstract;
702     function GetTransaction: ITransaction; virtual; abstract;
703     function GetErrorPrefix: string; virtual; abstract;
704     function TokenFound(var token: TSQLTokens): boolean; override;
705     procedure Reset; override;
706     procedure ShowError(msg: string; params: array of const); overload; virtual;
707     procedure ShowError(msg: string); overload;
708     public
709     constructor Create;
710     procedure FreeDataObjects;
711     class function FormatBlob(Field: ISQLData): string; overload;
712     class function FormatBlob(contents: string; subtype:integer): string; overload;
713     class function FormatArray(ar: IArray): string;
714     property BlobData[index: integer]: TBlobData read GetBlobData;
715     property BlobDataCount: integer read GetBlobDataCount;
716     property ArrayData[index: integer]: TArrayData read GetArrayData;
717     property ArrayDataCount: integer read GetArrayDataCount;
718     property Attachment: IAttachment read GetAttachment;
719     property Transaction: ITransaction read GetTransaction;
720     property OnProgressEvent: TOnProgressEvent read FOnProgressEvent write FOnProgressEvent; {Progress Bar Support}
721     end;
722    
723     TJnlEntryType = (jeTransStart, jeTransCommit, jeTransCommitRet, jeTransRollback,
724     jeTransRollbackRet, jeTransEnd, jeQuery,jeUnknown);
725    
726     TJnlEntry = record
727     JnlEntryType: TJnlEntryType;
728     Timestamp: TDateTime;
729     SessionID: integer;
730     TransactionID: integer;
731     OldTransactionID: integer;
732     TransactionName: AnsiString;
733     TPB: ITPB;
734     DefaultCompletion: TTransactionCompletion;
735     QueryText: AnsiString;
736     end;
737    
738     TOnNextJournalEntry = procedure(JnlEntry: TJnlEntry) of object;
739    
740     { TJournalProcessor - used to parse a client side journal}
741    
742     TJournalProcessor = class(TSQLTokeniser)
743     private
744     type TLineState = (lsInit, lsJnlFound, lsGotTimestamp, lsGotJnlType, lsGotSessionID,
745     lsGotTransactionID, lsGotOldTransactionID, lsGotText1Length,
746     lsGotText1, lsGotText2Length, lsGotText2);
747     private
748     FOnNextJournalEntry: TOnNextJournalEntry;
749     FInStream: TStream;
750     FFirebirdClientAPI: IFirebirdAPI;
751     procedure DoExecute;
752     function IdentifyJnlEntry(aTokenText: AnsiString): TJnlEntryType;
753     protected
754     function GetChar: AnsiChar; override;
755     property OnNextJournalEntry: TOnNextJournalEntry read FOnNextJournalEntry write FOnNextJournalEntry;
756     public
757     destructor Destroy; override;
758     class procedure Execute( aFileName: string; api: IFirebirdAPI; aOnNextJournalEntry: TOnNextJournalEntry);
759     class function JnlEntryText(je: TJnlEntryType): string;
760     end;
761    
762    
763 tony 45 function Max(n1, n2: Integer): Integer;
764     function Min(n1, n2: Integer): Integer;
765 tony 56 function RandomString(iLength: Integer): AnsiString;
766 tony 45 function RandomInteger(iLow, iHigh: Integer): Integer;
767 tony 56 function StripString(st: AnsiString; CharsToStrip: AnsiString): AnsiString;
768     function ExtractIdentifier(Dialect: Integer; Value: AnsiString): AnsiString;
769 tony 263 function FindReservedWord(w: AnsiString; var token: TSQLTokens): boolean;
770     function IsReservedWord(w: AnsiString): boolean;
771 tony 56 function QuoteIdentifier(Dialect: Integer; Value: AnsiString): AnsiString;
772     function QuoteIdentifierIfNeeded(Dialect: Integer; Value: AnsiString): AnsiString;
773     function Space2Underscore(s: AnsiString): AnsiString;
774     function SQLSafeString(const s: AnsiString): AnsiString;
775 tony 107 function IsSQLIdentifier(Value: AnsiString): boolean;
776 tony 117 function ExtractConnectString(const CreateSQL: AnsiString; var ConnectString: AnsiString): boolean;
777 tony 143 function MakeConnectString(ServerName, DatabaseName: AnsiString; Protocol: TProtocol;
778     PortNo: AnsiString = ''): AnsiString;
779     function ParseConnectString(ConnectString: AnsiString;
780     var ServerName, DatabaseName: AnsiString; var Protocol: TProtocolAll;
781     var PortNo: AnsiString): boolean;
782     function GetProtocol(ConnectString: AnsiString): TProtocolAll;
783 tony 45
784 tony 315 {$IF declared(TFormatSettings)}
785     function ParseDateTimeTZString(aDateTimeStr: Ansistring; var aDateTime: TDateTime;
786     var aTimezone: AnsiString; aFormatSettings: TFormatSettings; TimeOnly: boolean=false): boolean; overload;
787     {$IFEND}
788     function ParseDateTimeTZString(aDateTimeStr: Ansistring; var aDateTime: TDateTime;
789     var aTimezone: AnsiString; TimeOnly: boolean=false): boolean; overload;
790     procedure FBDecodeTime(aTime: TDateTime; var Hour, Minute, Second: word; var DeciMillisecond: cardinal);
791     function FBEncodeTime(Hour, Minute, Second, DeciMillisecond: cardinal): TDateTime;
792     function FBFormatDateTime(fmt: AnsiString; aDateTime: TDateTime): AnsiString;
793     function FormatTimeZoneOffset(EffectiveTimeOffsetMins: integer): AnsiString;
794     function DecodeTimeZoneOffset(TZOffset: AnsiString; var dstOffset: integer): boolean;
795     function StripLeadingZeros(Value: AnsiString): AnsiString;
796 tony 353 function TryStrToNumeric(S: Ansistring; out Value: int64; out scale: integer): boolean;
797     function NumericToDouble(aValue: Int64; aScale: integer): double;
798 tony 363 function StringToHex(octetString: string; MaxLineLength: integer=0): string; overload;
799     procedure StringToHex(octetString: string; TextOut: TStrings; MaxLineLength: integer=0); overload;
800 tony 315
801 tony 353
802 tony 45 implementation
803    
804 tony 353 uses FBMessages, Math
805 tony 263
806 tony 315 {$IFDEF FPC}
807 tony 263 ,RegExpr
808 tony 315 {$ELSE}
809     {$IF declared(CompilerVersion) and (CompilerVersion >= 22)}
810     , RegularExpressions
811     {$IFEND}
812 tony 263 {$ENDIF};
813 tony 117
814 tony 363 resourcestring
815     sXMLStackUnderflow = 'XML Stack Underflow';
816     sInvalidEndTag = 'XML End Tag Mismatch - %s';
817     sBadEndTagClosing = 'XML End Tag incorrectly closed';
818     sXMLStackOverFlow = 'XML Stack Overflow';
819     sXMLAttributeError = 'Unexpected attribute - "%s" = "%s"';
820     sInvalidBoundsList = 'Invalid array bounds list - "%s"';
821     sBinaryBlockMustbeEven = 'Binary block must have an even number of characters';
822     sArrayIndexError = 'Array Index Error (%d)';
823     sBlobIndexError = 'Blob Index Error (%d)';
824     sNoDatabase = 'Missing database for xml tag import';
825     sNoTransaction = 'Missing transaction for xml tag import';
826 tony 315
827 tony 363
828 tony 45 function Max(n1, n2: Integer): Integer;
829     begin
830     if (n1 > n2) then
831     result := n1
832     else
833     result := n2;
834     end;
835    
836     function Min(n1, n2: Integer): Integer;
837     begin
838     if (n1 < n2) then
839     result := n1
840     else
841     result := n2;
842     end;
843    
844 tony 56 function RandomString(iLength: Integer): AnsiString;
845 tony 45 begin
846     result := '';
847     while Length(result) < iLength do
848     result := result + IntToStr(RandomInteger(0, High(Integer)));
849     if Length(result) > iLength then
850     result := Copy(result, 1, iLength);
851     end;
852    
853     function RandomInteger(iLow, iHigh: Integer): Integer;
854     begin
855     result := Trunc(Random(iHigh - iLow)) + iLow;
856     end;
857    
858 tony 56 function StripString(st: AnsiString; CharsToStrip: AnsiString): AnsiString;
859 tony 45 var
860     i: Integer;
861     begin
862     result := '';
863     for i := 1 to Length(st) do begin
864     if AnsiPos(st[i], CharsToStrip) = 0 then
865     result := result + st[i];
866     end;
867     end;
868    
869 tony 117 {Extracts SQL Identifier typically from a Dialect 3 encoding}
870 tony 45
871 tony 117 function ExtractIdentifier(Dialect: Integer; Value: AnsiString): AnsiString;
872 tony 45 begin
873     Value := Trim(Value);
874     if Dialect = 1 then
875     Value := AnsiUpperCase(Value)
876     else
877     begin
878     if (Value <> '') and (Value[1] = '"') then
879     begin
880     Delete(Value, 1, 1);
881     Delete(Value, Length(Value), 1);
882     Value := StringReplace (Value, '""', '"', [rfReplaceAll]);
883     end
884     else
885     Value := AnsiUpperCase(Value);
886     end;
887     Result := Value;
888     end;
889    
890 tony 263 {Returns true if "w" is a Firebird SQL reserved word, and the
891     corresponding TSQLTokens value.}
892    
893     function FindReservedWord(w: AnsiString; var token: TSQLTokens): boolean;
894     var i: TSQLTokens;
895     begin
896     Result := true;
897     w := AnsiUpperCase(Trim(w));
898     for i := Low(TSQLReservedWords) to High(TSQLReservedWords) do
899     begin
900     if w = sqlReservedWords[i] then
901     begin
902     token := i;
903     Exit;
904     end;
905     if w < sqlReservedWords[i] then
906     break;
907     end;
908     Result := false;
909     end;
910    
911 tony 117 {Returns true if "w" is a Firebird SQL reserved word}
912 tony 45
913 tony 56 function IsReservedWord(w: AnsiString): boolean;
914 tony 263 var token: TSQLTokens;
915 tony 45 begin
916 tony 263 Result := FindReservedWord(w,token);
917 tony 45 end;
918    
919 tony 117 {Format an SQL Identifier according to SQL Dialect}
920    
921 tony 56 function QuoteIdentifier(Dialect: Integer; Value: AnsiString): AnsiString;
922 tony 45 begin
923 tony 311 Value := TrimRight(Value);
924 tony 45 if Dialect = 1 then
925 tony 311 Value := AnsiUpperCase(Value)
926 tony 45 else
927 tony 117 Value := '"' + StringReplace (Value, '""', '"', [rfReplaceAll]) + '"';
928 tony 45 Result := Value;
929     end;
930    
931 tony 107 const
932     ValidSQLIdentifierChars = ['A'..'Z','a'..'z','0'..'9','_','$'];
933    
934 tony 117 {Returns true if the value is a valid SQL Identifier - note lower case accepted}
935    
936 tony 107 function IsSQLIdentifier(Value: AnsiString): boolean;
937     var i: integer;
938     begin
939     Result := false;
940     for i := 1 to Length(Value) do
941     if not (Value[i] in ValidSQLIdentifierChars) then Exit;
942     Result := true;
943     end;
944    
945 tony 315 function SchemeToProtocol(scheme: AnsiString): TProtocolAll;
946     begin
947     scheme := AnsiUpperCase(scheme);
948     if scheme = 'INET' then
949     Result := inet
950     else
951     if scheme = 'INET4' then
952     Result := inet4
953     else
954     if scheme = 'INET6' then
955     Result := inet6
956     else
957     if scheme = 'XNET' then
958     Result := xnet
959     else
960     if scheme = 'WNET' then
961     Result := wnet
962     end;
963    
964 tony 117 {Extracts the Database Connect string from a Create Database Statement}
965    
966 tony 315 {$IF declared(TRegexpr)}
967 tony 117 function ExtractConnectString(const CreateSQL: AnsiString;
968     var ConnectString: AnsiString): boolean;
969     var RegexObj: TRegExpr;
970     begin
971     RegexObj := TRegExpr.Create;
972     try
973     {extact database file spec}
974     RegexObj.ModifierG := false; {turn off greedy matches}
975     RegexObj.ModifierI := true; {case insensitive match}
976     RegexObj.Expression := '^ *CREATE +(DATABASE|SCHEMA) +''(.*)''';
977     Result := RegexObj.Exec(CreateSQL);
978     if Result then
979 tony 143 ConnectString := RegexObj.Match[2];
980 tony 117 finally
981     RegexObj.Free;
982     end;
983     end;
984 tony 143
985     function ParseConnectString(ConnectString: AnsiString; var ServerName,
986     DatabaseName: AnsiString; var Protocol: TProtocolAll; var PortNo: AnsiString
987     ): boolean;
988 tony 231
989 tony 143 var RegexObj: TRegExpr;
990     begin
991     ServerName := '';
992     DatabaseName := ConnectString;
993     PortNo := '';
994     Protocol := unknownProtocol;
995     RegexObj := TRegExpr.Create;
996     try
997     {extact database file spec}
998     RegexObj.ModifierG := false; {turn off greedy matches}
999 tony 231 RegexObj.Expression := '^([a-zA-Z46]+)://([a-zA-Z0-9\-\.]*)(|:[0-9a-zA-Z\-]+)/(.*)$';
1000 tony 143 Result := RegexObj.Exec(ConnectString);
1001     if Result then
1002     begin
1003     {URL type connect string}
1004 tony 315 Protocol := SchemeToProtocol(RegexObj.Match[1]);
1005 tony 143 ServerName := RegexObj.Match[2];
1006     if RegexObj.MatchLen[3] > 0 then
1007     PortNo := system.Copy(ConnectString,RegexObj.MatchPos[3]+1,RegexObj.MatchLen[3]-1);
1008     DatabaseName := RegexObj.Match[4];
1009 tony 231 if ServerName = '' then
1010     DatabaseName := '/' + DatabaseName;
1011 tony 143 end
1012     else
1013     begin
1014 tony 231 {URL type connect string - local loop}
1015     RegexObj.Expression := '^([a-zA-Z46]+)://(.*)$';
1016 tony 143 Result := RegexObj.Exec(ConnectString);
1017     if Result then
1018 tony 231 begin
1019 tony 315 Protocol := SchemeToProtocol(RegexObj.Match[1]);
1020 tony 231 DatabaseName := RegexObj.Match[2];
1021     end
1022 tony 143 else
1023     begin
1024 tony 231 RegexObj.Expression := '^([a-zA-Z]:\\.*)';
1025 tony 143 Result := RegexObj.Exec(ConnectString);
1026     if Result then
1027 tony 231 Protocol := Local {Windows with leading drive ID}
1028 tony 143 else
1029     begin
1030 tony 231 RegexObj.Expression := '^([a-zA-Z0-9\-\.]+)(|/[0-9a-zA-Z\-]+):(.*)$';
1031 tony 143 Result := RegexObj.Exec(ConnectString);
1032     if Result then
1033     begin
1034 tony 231 {Legacy TCP Format}
1035 tony 143 ServerName := RegexObj.Match[1];
1036     if RegexObj.MatchLen[2] > 0 then
1037     PortNo := system.Copy(ConnectString,RegexObj.MatchPos[2]+1,RegexObj.MatchLen[2]-1);
1038     DatabaseName := RegexObj.Match[3];
1039 tony 231 Protocol := TCP;
1040 tony 143 end
1041     else
1042     begin
1043 tony 231 RegexObj.Expression := '^\\\\([a-zA-Z0-9\-\.]+)(|@[0-9a-zA-Z\-]+)\\(.*)$';
1044     Result := RegexObj.Exec(ConnectString);
1045     if Result then
1046     begin
1047     {Netbui}
1048     ServerName := RegexObj.Match[1];
1049     if RegexObj.MatchLen[2] > 0 then
1050     PortNo := system.Copy(ConnectString,RegexObj.MatchPos[2]+1,RegexObj.MatchLen[2]-1);
1051     DatabaseName := RegexObj.Match[3];
1052     Protocol := NamedPipe
1053     end
1054     else
1055     begin
1056     Result := true;
1057     Protocol := Local; {Assume local}
1058     end;
1059 tony 143 end;
1060     end;
1061     end;
1062     end;
1063     finally
1064     RegexObj.Free;
1065     end;
1066     end;
1067    
1068 tony 315 {$ELSE}
1069     {$IF declared(TRegex)}
1070     function ExtractConnectString(const CreateSQL: AnsiString;
1071     var ConnectString: AnsiString): boolean;
1072     var Regex: TRegEx;
1073     Match: TMatch;
1074 tony 143 begin
1075 tony 315 Regex := TRegEx.Create('^ *CREATE +(DATABASE|SCHEMA) +''(.*)''',[roIgnoreCase]);
1076     {extact database file spec}
1077     Match := Regex.Match(CreateSQL);
1078     Result := Match.Success and (Match.Groups.Count = 3);
1079     if Result then
1080     ConnectString := Match.Groups[2].Value;
1081 tony 143 end;
1082    
1083 tony 315 function ParseConnectString(ConnectString: AnsiString; var ServerName,
1084     DatabaseName: AnsiString; var Protocol: TProtocolAll; var PortNo: AnsiString
1085     ): boolean;
1086    
1087     var Regex: TRegEx;
1088     Match: TMatch;
1089     begin
1090     ServerName := '';
1091     DatabaseName := ConnectString;
1092     PortNo := '';
1093     Protocol := unknownProtocol;
1094     {extact database file spec}
1095     Match := Regex.Match(ConnectString,'^([a-zA-Z46]+)://([a-zA-Z0-9\-\.]*)(|:[0-9a-zA-Z\-]+)/(.*)$',[roIgnoreCase]);
1096     Result := Match.Success and (Match.Groups.Count = 5);
1097     if Result then
1098     begin
1099     {URL type connect string}
1100     Protocol := SchemeToProtocol(Match.Groups[1].Value);
1101     ServerName := Match.Groups[2].Value;
1102     PortNo := Match.Groups[3].Value;
1103     DatabaseName := Match.Groups[4].Value;
1104     if ServerName = '' then
1105     DatabaseName := '/' + DatabaseName;
1106     end
1107     else
1108     begin
1109     {URL type connect string - local loop}
1110     Match := Regex.Match(ConnectString,'^([a-zA-Z46]+)://(.*)$',[roIgnoreCase]);
1111     Result := Match.Success and (Match.Groups.Count = 3);
1112     if Result then
1113     begin
1114     Protocol := SchemeToProtocol(Match.Groups[1].Value);
1115     DatabaseName := Match.Groups[2].Value;
1116     end
1117     else
1118     begin
1119     Match := Regex.Match(ConnectString,'^([a-zA-Z]:\\.*)',[roIgnoreCase]);
1120     Result := Match.Success;
1121     if Result then
1122     Protocol := Local {Windows with leading drive ID}
1123     else
1124     begin
1125     Match := Regex.Match(ConnectString,'^([a-zA-Z0-9\-\.]+)(|/[0-9a-zA-Z\-]+):(.*)$',[roIgnoreCase]);
1126     Result := Match.Success and (Match.Groups.Count = 4);
1127     if Result then
1128     begin
1129     {Legacy TCP Format}
1130     ServerName := Match.Groups[1].Value;
1131     PortNo := Match.Groups[2].Value;
1132     DatabaseName := Match.Groups[3].Value;
1133     Protocol := TCP;
1134     end
1135     else
1136     begin
1137     Match := Regex.Match(ConnectString,'^\\\\([a-zA-Z0-9\-\.]+)(|@[0-9a-zA-Z\-]+)\\(.*)$',[roIgnoreCase]);
1138     Result := Match.Success and (Match.Groups.Count = 4);
1139     if Result then
1140     begin
1141     {Netbui}
1142     ServerName := Match.Groups[1].Value;
1143     PortNo := Match.Groups[2].Value;
1144     DatabaseName := Match.Groups[3].Value;
1145     Protocol := NamedPipe
1146     end
1147     else
1148     begin
1149     Result := true;
1150     Protocol := Local; {Assume local}
1151     end;
1152     end;
1153     end;
1154     end;
1155     end;
1156     end;
1157 tony 118 {$ELSE}
1158 tony 315 {cruder version of above for Delphi < XE. Older versions lack regular expression
1159 tony 121 handling.}
1160 tony 118 function ExtractConnectString(const CreateSQL: AnsiString;
1161     var ConnectString: AnsiString): boolean;
1162     var i: integer;
1163     begin
1164     Result := false;
1165     i := Pos('''',CreateSQL);
1166     if i > 0 then
1167     begin
1168     ConnectString := CreateSQL;
1169     delete(ConnectString,1,i);
1170     i := Pos('''',ConnectString);
1171     if i > 0 then
1172     begin
1173     delete(ConnectString,i,Length(ConnectString)-i+1);
1174     Result := true;
1175     end;
1176     end;
1177     end;
1178 tony 143
1179     function ParseConnectString(ConnectString: AnsiString;
1180     var ServerName, DatabaseName: AnsiString; var Protocol: TProtocolAll;
1181     var PortNo: AnsiString): boolean;
1182     begin
1183     Result := false;
1184     end;
1185    
1186 tony 315 {$IFEND}
1187     {$IFEND}
1188 tony 117
1189 tony 315 function GetProtocol(ConnectString: AnsiString): TProtocolAll;
1190     var ServerName,
1191     DatabaseName: AnsiString;
1192     PortNo: AnsiString;
1193     begin
1194     if not ParseConnectString(ConnectString,ServerName,DatabaseName,Result,PortNo) then
1195     Result := unknownProtocol;
1196     end;
1197    
1198 tony 143 {Make a connect string in format appropriate protocol}
1199    
1200     function MakeConnectString(ServerName, DatabaseName: AnsiString;
1201     Protocol: TProtocol; PortNo: AnsiString): AnsiString;
1202 tony 231
1203     function FormatURL: AnsiString;
1204     begin
1205     if (ServerName = '') and (Pos('/',DatabaseName) <= 1) then
1206     Result := DatabaseName
1207     else
1208     Result := ServerName + '/' + DatabaseName;
1209     end;
1210    
1211 tony 143 begin
1212 tony 315 if ServerName = '' then ServerName := 'localhost';
1213 tony 143 if PortNo <> '' then
1214     case Protocol of
1215     NamedPipe:
1216     ServerName := ServerName + '@' + PortNo;
1217     Local,
1218     SPX,
1219     xnet: {do nothing};
1220     TCP:
1221     ServerName := ServerName + '/' + PortNo;
1222     else
1223     ServerName := ServerName + ':' + PortNo;
1224     end;
1225    
1226     case Protocol of
1227     TCP: Result := ServerName + ':' + DatabaseName; {do not localize}
1228     SPX: Result := ServerName + '@' + DatabaseName; {do not localize}
1229     NamedPipe: Result := '\\' + ServerName + '\' + DatabaseName; {do not localize}
1230     Local: Result := DatabaseName; {do not localize}
1231 tony 231 inet: Result := 'inet://' + FormatURL; {do not localize}
1232     inet4: Result := 'inet4://' + FormatURL; {do not localize}
1233     inet6: Result := 'inet6://' + FormatURL; {do not localize}
1234     wnet: Result := 'wnet://' + FormatURL; {do not localize}
1235     xnet: Result := 'xnet://' + FormatURL; {do not localize}
1236 tony 143 end;
1237     end;
1238    
1239 tony 117 {Format an SQL Identifier according to SQL Dialect with encapsulation if necessary}
1240    
1241 tony 56 function QuoteIdentifierIfNeeded(Dialect: Integer; Value: AnsiString): AnsiString;
1242 tony 45 begin
1243 tony 311 Value := TrimRight(Value);
1244 tony 45 if (Dialect = 3) and
1245 tony 115 (IsReservedWord(Value) or not IsSQLIdentifier(Value) or (AnsiUpperCase(Value) <> Value)) then
1246 tony 117 Result := '"' + StringReplace (TrimRight(Value), '"', '""', [rfReplaceAll]) + '"'
1247 tony 45 else
1248     Result := Value
1249     end;
1250    
1251 tony 117 {Replaces unknown characters in a string with underscores}
1252    
1253 tony 56 function Space2Underscore(s: AnsiString): AnsiString;
1254 tony 45 var
1255     k: integer;
1256     begin
1257     Result := s;
1258     for k := 1 to Length(s) do
1259 tony 117 if not (Result[k] in ValidSQLIdentifierChars) then
1260 tony 45 Result[k] := '_';
1261     end;
1262    
1263 tony 117 {Reformats an SQL string with single quotes duplicated.}
1264    
1265 tony 56 function SQLSafeString(const s: AnsiString): AnsiString;
1266 tony 47 begin
1267     Result := StringReplace(s,'''','''''',[rfReplaceAll]);
1268     end;
1269 tony 45
1270 tony 270 { TSQLParamProcessor }
1271    
1272     function TSQLParamProcessor.DoExecute(GenerateParamNames: boolean;
1273     var slNames: TStrings): AnsiString;
1274     var token: TSQLTokens;
1275     iParamSuffix: Integer;
1276     begin
1277     Result := '';
1278     iParamSuffix := 0;
1279    
1280     while not EOF do
1281     begin
1282     token := GetNextToken;
1283     case token of
1284     sqltParam,
1285     sqltQuotedParam:
1286     begin
1287     Result := Result + '?';
1288     slNames.Add(TokenText);
1289     end;
1290    
1291     sqltPlaceHolder:
1292     if GenerateParamNames then
1293     begin
1294     Inc(iParamSuffix);
1295     slNames.AddObject(sIBXParam + IntToStr(iParamSuffix),self); //Note local convention
1296     //add pointer to self to mark entry
1297     Result := Result + '?';
1298     end
1299     else
1300     IBError(ibxeSQLParseError, [SParamNameExpected]);
1301    
1302     sqltQuotedString:
1303     Result := Result + '''' + SQLSafeString(TokenText) + '''';
1304    
1305     sqltIdentifierInDoubleQuotes:
1306     Result := Result + '"' + StringReplace(TokenText,'"','""',[rfReplaceAll]) + '"';
1307    
1308     sqltComment:
1309     Result := Result + '/*' + TokenText + '*/';
1310    
1311     sqltCommentLine:
1312 tony 287 Result := Result + '--' + TokenText + LineEnding;
1313 tony 270
1314     sqltEOL:
1315     Result := Result + LineEnding;
1316    
1317     else
1318     Result := Result + TokenText;
1319     end;
1320     end;
1321     end;
1322    
1323     function TSQLParamProcessor.GetChar: AnsiChar;
1324     begin
1325     if FIndex <= Length(FInString) then
1326     begin
1327     Result := FInString[FIndex];
1328     Inc(FIndex);
1329     end
1330     else
1331     Result := #0;
1332     end;
1333    
1334     class function TSQLParamProcessor.Execute(sSQL: AnsiString;
1335     GenerateParamNames: boolean; var slNames: TStrings): AnsiString;
1336     begin
1337     with self.Create do
1338     try
1339     FInString := sSQL;
1340     FIndex := 1;
1341     Result := DoExecute(GenerateParamNames,slNames);
1342     finally
1343     Free;
1344     end;
1345     end;
1346    
1347 tony 263 { TSQLwithNamedParamsTokeniser }
1348    
1349     procedure TSQLwithNamedParamsTokeniser.Assign(source: TSQLTokeniser);
1350     begin
1351     inherited Assign(source);
1352     if source is TSQLwithNamedParamsTokeniser then
1353     begin
1354     FState := TSQLwithNamedParamsTokeniser(source).FState;
1355     FNested := TSQLwithNamedParamsTokeniser(source).FNested;
1356     end;
1357     end;
1358    
1359     procedure TSQLwithNamedParamsTokeniser.Reset;
1360     begin
1361     inherited Reset;
1362     FState := stInit;
1363     FNested := 0;
1364     end;
1365    
1366     function TSQLwithNamedParamsTokeniser.TokenFound(var token: TSQLTokens
1367     ): boolean;
1368     begin
1369     Result := inherited TokenFound(token);
1370     if not Result then Exit;
1371    
1372     case FState of
1373     stInit:
1374     begin
1375     case token of
1376     sqltColon:
1377     begin
1378     FState := stInParam;
1379     ResetQueue(token);
1380     end;
1381    
1382     sqltBegin:
1383     begin
1384     FState := stInBlock;
1385     FNested := 1;
1386     end;
1387    
1388     sqltOpenSquareBracket:
1389     FState := stInArrayDim;
1390    
1391     end;
1392     end;
1393    
1394     stInParam:
1395     begin
1396     case token of
1397     sqltIdentifier:
1398     token := sqltParam;
1399    
1400     sqltIdentifierInDoubleQuotes:
1401     token := sqltQuotedParam;
1402    
1403     else
1404     begin
1405     QueueToken(token);
1406     ReleaseQueue(token);
1407     end;
1408     end;
1409     FState := stInit;
1410     end;
1411    
1412     stInBlock:
1413     begin
1414     case token of
1415 tony 348 sqltBegin,
1416     sqltCase:
1417 tony 263 Inc(FNested);
1418    
1419     sqltEnd:
1420     begin
1421     Dec(FNested);
1422     if FNested = 0 then
1423     FState := stInit;
1424     end;
1425     end;
1426     end;
1427    
1428     stInArrayDim:
1429     begin
1430     if token = sqltCloseSquareBracket then
1431     FState := stInit;
1432     end;
1433     end;
1434    
1435     Result := (FState <> stInParam);
1436     end;
1437    
1438     { TSQLTokeniser }
1439    
1440     function TSQLTokeniser.GetNext: TSQLTokens;
1441     var C: AnsiChar;
1442     begin
1443     if EOF then
1444     Result := sqltEOF
1445     else
1446     begin
1447     C := GetChar;
1448     case C of
1449     #0:
1450     Result := sqltEOF;
1451     ' ',TAB:
1452     Result := sqltSpace;
1453     '0'..'9':
1454     Result := sqltNumberString;
1455     ';':
1456     Result := sqltSemiColon;
1457     '?':
1458     Result := sqltPlaceholder;
1459     '|':
1460     Result := sqltPipe;
1461     '"':
1462     Result := sqltDoubleQuotes;
1463     '''':
1464     Result := sqltSingleQuotes;
1465     '/':
1466     Result := sqltForwardSlash;
1467 tony 270 '\':
1468     Result := sqltBackslash;
1469 tony 263 '*':
1470     Result := sqltAsterisk;
1471     '(':
1472     Result := sqltOpenBracket;
1473     ')':
1474     Result := sqltCloseBracket;
1475     ':':
1476     Result := sqltColon;
1477     ',':
1478     Result := sqltComma;
1479     '.':
1480     Result := sqltPeriod;
1481     '=':
1482     Result := sqltEquals;
1483     '[':
1484     Result := sqltOpenSquareBracket;
1485     ']':
1486     Result := sqltCloseSquareBracket;
1487 tony 287 '-':
1488     Result := sqltMinus;
1489 tony 263 '<':
1490     Result := sqltLT;
1491     '>':
1492     Result := sqltGT;
1493     CR:
1494     Result := sqltCR;
1495     LF:
1496     Result := sqltEOL;
1497     else
1498     if C in ValidSQLIdentifierChars then
1499     Result := sqltIdentifier
1500     else
1501     Result := sqltOtherCharacter;
1502     end;
1503     FLastChar := C
1504     end;
1505     FNextToken := Result;
1506     end;
1507    
1508     procedure TSQLTokeniser.PopQueue(var token: TSQLTokens);
1509     begin
1510     if FQFirst = FQLast then
1511     IBError(ibxeTokenQueueUnderflow,[]);
1512     token := FTokenQueue[FQFirst].token;
1513     FString := FTokenQueue[FQFirst].text;
1514     Inc(FQFirst);
1515     if FQFirst = FQLast then
1516     FQueueState := tsHold;
1517     end;
1518    
1519     procedure TSQLTokeniser.Assign(source: TSQLTokeniser);
1520     begin
1521     FString := source.FString;
1522     FNextToken := source.FNextToken;
1523     FTokenQueue := source.FTokenQueue;
1524     FQueueState := source.FQueueState;
1525     FQFirst := source.FQFirst;
1526     FQLast := source.FQLast;
1527     end;
1528    
1529     function TSQLTokeniser.TokenFound(var token: TSQLTokens): boolean;
1530     begin
1531     Result := (FState = stDefault);
1532     if Result and (token = sqltIdentifier) then
1533     FindReservedWord(FString,token);
1534     end;
1535    
1536     procedure TSQLTokeniser.QueueToken(token: TSQLTokens; text: AnsiString);
1537     begin
1538     if FQLast > TokenQueueMaxSize then
1539     IBError(ibxeTokenQueueOverflow,[]);
1540     FTokenQueue[FQLast].token := token;
1541     FTokenQueue[FQLast].text := text;
1542     Inc(FQLast);
1543     end;
1544    
1545     procedure TSQLTokeniser.QueueToken(token: TSQLTokens);
1546     begin
1547     QueueToken(token,TokenText);
1548     end;
1549    
1550     procedure TSQLTokeniser.ResetQueue;
1551     begin
1552     FQFirst := 0;
1553     FQLast := 0;
1554     FQueueState := tsHold;
1555     end;
1556    
1557     procedure TSQLTokeniser.ResetQueue(token: TSQLTokens; text: AnsiString);
1558     begin
1559     ResetQueue;
1560     QueueToken(token,text);
1561     end;
1562    
1563     procedure TSQLTokeniser.ResetQueue(token: TSQLTokens);
1564     begin
1565     ResetQueue;
1566     QueueToken(token);
1567     end;
1568    
1569     procedure TSQLTokeniser.ReleaseQueue(var token: TSQLTokens);
1570     begin
1571     FQueueState := tsRelease;
1572     PopQueue(token);
1573     end;
1574    
1575     procedure TSQLTokeniser.ReleaseQueue;
1576     begin
1577     FQueueState := tsRelease;
1578     end;
1579    
1580     function TSQLTokeniser.GetQueuedText: AnsiString;
1581     var i: integer;
1582     begin
1583     Result := '';
1584     for i := FQFirst to FQLast do
1585     Result := Result + FTokenQueue[i].text;
1586     end;
1587    
1588     procedure TSQLTokeniser.SetTokenText(text: AnsiString);
1589     begin
1590     FString := text;
1591     end;
1592    
1593     constructor TSQLTokeniser.Create;
1594     begin
1595     inherited Create;
1596     Reset;
1597     end;
1598    
1599     destructor TSQLTokeniser.Destroy;
1600     begin
1601     Reset;
1602     inherited Destroy;
1603     end;
1604    
1605     procedure TSQLTokeniser.Reset;
1606     begin
1607     FNextToken := sqltInit;
1608     FState := stDefault;
1609     FString := '';
1610     FEOF := false;
1611     ResetQueue;
1612     end;
1613    
1614 tony 359 function TSQLTokeniser.ReadCharacters(NumOfChars: integer): AnsiString;
1615     var i: integer;
1616     begin
1617     Result := FLastChar;
1618     for i := 2 to NumOfChars do
1619     begin
1620 tony 363 if GetNext = sqltEOF then Exit;
1621 tony 359 Result := Result + FLastChar;
1622     end;
1623 tony 363 GetNext;
1624 tony 359 end;
1625    
1626 tony 263 function TSQLTokeniser.GetNextToken: TSQLTokens;
1627     begin
1628     if FQueueState = tsRelease then
1629     repeat
1630     PopQueue(Result);
1631     FEOF := Result = sqltEOF;
1632     if TokenFound(Result) then
1633     Exit;
1634     until FQueueState <> tsRelease;
1635    
1636     Result := InternalGetNextToken;
1637     end;
1638    
1639     {a simple lookahead one algorithm to extra the next symbol}
1640    
1641     function TSQLTokeniser.InternalGetNextToken: TSQLTokens;
1642     var C: AnsiChar;
1643     begin
1644     Result := sqltEOF;
1645    
1646     if FNextToken = sqltInit then
1647     GetNext;
1648    
1649     repeat
1650 tony 353 if FSkipNext then
1651     begin
1652     FSkipNext := false;
1653     GetNext;
1654     end;
1655    
1656 tony 263 Result := FNextToken;
1657     C := FLastChar;
1658     GetNext;
1659    
1660 tony 353 if (Result = sqltCR) and (FNextToken = sqltEOL) then
1661 tony 263 begin
1662 tony 353 FSkipNext := true;
1663     Result := sqltEOL;
1664     C := LF;
1665 tony 263 end;
1666    
1667     case FState of
1668     stInComment:
1669     begin
1670     if (Result = sqltAsterisk) and (FNextToken = sqltForwardSlash) then
1671     begin
1672     FState := stDefault;
1673     Result := sqltComment;
1674     GetNext;
1675     end
1676     else
1677 tony 353 if Result = sqltEOL then
1678     FString := FString + LineEnding
1679     else
1680 tony 263 FString := FString + C;
1681     end;
1682    
1683     stInCommentLine:
1684     begin
1685     case Result of
1686     sqltEOL:
1687     begin
1688     FState := stDefault;
1689     Result := sqltCommentLine;
1690     end;
1691    
1692     else
1693     FString := FString + C;
1694     end;
1695     end;
1696    
1697     stSingleQuoted:
1698     begin
1699     if (Result = sqltSingleQuotes) then
1700     begin
1701     if (FNextToken = sqltSingleQuotes) then
1702     begin
1703     FSkipNext := true;
1704     FString := FString + C;
1705     end
1706     else
1707     begin
1708     Result := sqltQuotedString;
1709     FState := stDefault;
1710     end;
1711     end
1712     else
1713 tony 353 if Result = sqltEOL then
1714     FString := FString + LineEnding
1715     else
1716 tony 263 FString := FString + C;
1717     end;
1718    
1719     stDoubleQuoted:
1720     begin
1721     if (Result = sqltDoubleQuotes) then
1722     begin
1723     if (FNextToken = sqltDoubleQuotes) then
1724     begin
1725     FSkipNext := true;
1726     FString := FString + C;
1727     end
1728     else
1729     begin
1730     Result := sqltIdentifierInDoubleQuotes;
1731     FState := stDefault;
1732     end;
1733     end
1734     else
1735 tony 353 if Result = sqltEOL then
1736     FString := FString + LineEnding
1737     else
1738 tony 263 FString := FString + C;
1739     end;
1740    
1741     stInIdentifier:
1742     begin
1743     FString := FString + C;
1744     Result := sqltIdentifier;
1745     if not (FNextToken in [sqltIdentifier,sqltNumberString]) then
1746     FState := stDefault
1747     end;
1748    
1749     stInNumeric:
1750     begin
1751     FString := FString + C;
1752     if (Result = sqltPeriod) and (FNextToken = sqltPeriod) then
1753     begin
1754     {malformed decimal}
1755     FState := stInIdentifier;
1756     Result := sqltIdentifier
1757     end
1758     else
1759     begin
1760     if not (FNextToken in [sqltNumberString,sqltPeriod]) then
1761     FState := stDefault;
1762     Result := sqltNumberString;
1763     end;
1764     end;
1765    
1766     else {stDefault}
1767     begin
1768     FString := C;
1769     case Result of
1770    
1771     sqltPipe:
1772     if FNextToken = sqltPipe then
1773     begin
1774     Result := sqltConcatSymbol;
1775     FString := C + FLastChar;
1776     GetNext;
1777     end;
1778    
1779     sqltForwardSlash:
1780     begin
1781     if FNextToken = sqltAsterisk then
1782     begin
1783     FString := '';
1784     GetNext;
1785     FState := stInComment;
1786     end
1787 tony 287 end;
1788    
1789     sqltMinus:
1790     begin
1791     if FNextToken = sqltMinus then
1792 tony 263 begin
1793     FString := '';
1794     GetNext;
1795     FState := stInCommentLine;
1796     end;
1797     end;
1798    
1799     sqltSingleQuotes:
1800     begin
1801     FString := '';
1802     FState := stSingleQuoted;
1803     end;
1804    
1805     sqltDoubleQuotes:
1806     begin
1807     FString := '';
1808     FState := stDoubleQuoted;
1809     end;
1810    
1811     sqltIdentifier:
1812 tony 265 if FNextToken in [sqltIdentifier,sqltNumberString] then
1813 tony 263 FState := stInIdentifier;
1814    
1815     sqltNumberString:
1816     if FNextToken in [sqltNumberString,sqltPeriod] then
1817     FState := stInNumeric;
1818 tony 353
1819     sqltEOL:
1820     FString := LineEnding;
1821 tony 263 end;
1822     end;
1823     end;
1824    
1825     // writeln(FString);
1826     FEOF := Result = sqltEOF;
1827     until TokenFound(Result) or EOF;
1828     end;
1829    
1830 tony 315 function ParseDateTimeTZString(aDateTimeStr: Ansistring; var aDateTime: TDateTime;
1831     var aTimezone: AnsiString; TimeOnly: boolean): boolean;
1832     {$IF declared(TFormatSettings)}
1833     begin
1834     {$IF declared(DefaultFormatSettings)}
1835     Result := ParseDateTimeTZString(aDateTimeStr,aDateTime,aTimeZone,DefaultFormatSettings,TimeOnly);
1836     {$ELSE}
1837     {$IF declared(FormatSettings)}
1838     Result := ParseDateTimeTZString(aDateTimeStr,aDateTime,aTimeZone,FormatSettings,TimeOnly);
1839     {$IFEND} {$IFEND}
1840     end;
1841    
1842     function ParseDateTimeTZString(aDateTimeStr: Ansistring; var aDateTime: TDateTime;
1843     var aTimezone: AnsiString; aFormatSettings: TFormatSettings; TimeOnly: boolean=false): boolean;
1844     {$IFEND}
1845     const
1846     whitespacechars = [' ',#$09,#$0A,#$0D];
1847     var i,j,l: integer;
1848     aTime: TDateTime;
1849     DMs: longint;
1850     begin
1851     Result := false;
1852     aTimezone := '';
1853     if aDateTimeStr <> '' then
1854     {$if declared(TFormatSettings)}
1855     with aFormatSettings do
1856     {$IFEND}
1857     begin
1858     aDateTime := 0;
1859     {Parse to get time zone info}
1860     i := 1;
1861     while (i <= length(aDateTimeStr)) and (aDateTimeStr[i] in whitespacechars) do inc(i); {skip white space}
1862     if not TimeOnly then
1863     begin
1864     {decode date}
1865     j := i;
1866     while (j <= length(aDateTimeStr)) and (aDateTimeStr[j] in ['0'..'9',DateSeparator]) do inc(j);
1867     if TryStrToDate(system.copy(aDateTimeStr,i,j-i),aDateTime) then
1868     i := j; {otherwise start again i.e. assume time only}
1869     end;
1870    
1871     while (i <= length(aDateTimeStr)) and (aDateTimeStr[i] in whitespacechars) do inc(i); {skip white space}
1872     {decode time}
1873     j := i;
1874     while (j <= length(aDateTimeStr)) and (aDateTimeStr[j] in ['0'..'9',TimeSeparator]) do inc(j);
1875     Result := TryStrToTime(system.copy(aDateTimeStr,i,j-i),aTime);
1876     if not Result then Exit;
1877     aDateTime := aDateTime + aTime;
1878     i := j;
1879    
1880     {is there a factional second part}
1881     if (i <= length(aDateTimeStr)) and (aDateTimeStr[i] = '.') then
1882     begin
1883     inc(i);
1884     inc(j);
1885     while (j <= Length(aDateTimeStr)) and (aDateTimeStr[j] in ['0'..'9']) do inc(j);
1886     if j > i then
1887     begin
1888     l := j-i;
1889     if l > 4 then l := 4;
1890     Result := TryStrToInt(system.copy(aDateTimeStr,i,l),DMs);
1891     if not Result then Exit;
1892    
1893     {adjust for number of significant digits}
1894     case l of
1895     3: DMs := DMs * 10;
1896     2: DMs := DMs * 100;
1897     1: DMs := DMs * 1000;
1898     end;
1899     aDateTime := aDateTime + (DMs / (MsecsPerDay*10));
1900     end;
1901     end;
1902     i := j;
1903    
1904     while (i <= length(aDateTimeStr)) and (aDateTimeStr[i] in whitespacechars) do inc(i); {skip white space}
1905     {decode time zone}
1906     if i < length(aDateTimeStr) then
1907     begin
1908     j := i;
1909     while (j <= length(aDateTimeStr)) and not (aDateTimeStr[j] in whitespacechars) do inc(j);
1910     aTimezone := system.copy(aDateTimeStr,i,j-i);
1911     end;
1912     Result := true;
1913     end
1914     end;
1915    
1916     {The following is similar to FPC DecodeTime except that the Firebird standard
1917     decimilliseconds is used instead of milliseconds for fractional seconds}
1918    
1919     procedure FBDecodeTime(aTime: TDateTime; var Hour, Minute, Second: word;
1920     var DeciMillisecond: cardinal);
1921     var D : Double;
1922     l : cardinal;
1923     begin
1924     {conversion to decimilliseconds hacked from FPC DateTimeToTimeStamp}
1925     D := aTime * MSecsPerDay *10;
1926     if D < 0 then
1927     D := D - 0.5
1928     else
1929     D := D + 0.5;
1930     {rest hacked from FPC DecodeTIme}
1931     l := Abs(Trunc(D)) Mod (MSecsPerDay*10);
1932     Hour := l div 36000000;
1933     l := l mod 36000000;
1934     Minute := l div 600000;
1935     l := l mod 600000;
1936     Second := l div 10000;
1937     DeciMillisecond := l mod 10000;
1938     end;
1939    
1940     {The following is similar to FPC EncodeTime except that the Firebird standard
1941     decimilliseconds is used instead of milliseconds for fractional seconds}
1942    
1943     function FBEncodeTime(Hour, Minute, Second, DeciMillisecond: cardinal): TDateTime;
1944     const DMSecsPerDay = MSecsPerDay*10;
1945     var DMs: cardinal;
1946     D: Double;
1947     begin
1948     if (Hour<24) and (Minute<60) and (Second<60) and (DeciMillisecond<10000) then
1949     begin
1950     DMs := Hour*36000000+Minute*600000+Second*10000+DeciMillisecond;
1951     D := DMs/DMSecsPerDay;
1952     Result:=TDateTime(d)
1953     end
1954     else
1955     IBError(ibxeBadTimeSpecification,[Hour, Minute, Second, DeciMillisecond]);
1956     end;
1957    
1958     {The following is similar to FPC FormatDateTime except that it additionally
1959     allows the timstamp to have a fractional seconds component with a resolution
1960     of four decimal places. This is appended to the result for FormatDateTime
1961     if the format string contains a "zzzz' string.}
1962    
1963     function FBFormatDateTime(fmt: AnsiString; aDateTime: TDateTime): AnsiString;
1964     var Hour, Minute, Second: word;
1965     DeciMillisecond: cardinal;
1966     begin
1967     if Pos('zzzz',fmt) > 0 then
1968     begin
1969     FBDecodeTime(aDateTime, Hour, Minute, Second, DeciMillisecond);
1970     fmt := StringReplace(fmt, 'zzzz', Format('%.4d',[DeciMillisecond]), [rfReplaceAll]);
1971     end;
1972     Result := FormatDateTime(fmt,aDateTime);
1973     end;
1974    
1975     function FormatTimeZoneOffset(EffectiveTimeOffsetMins: integer): AnsiString;
1976     begin
1977     if EffectiveTimeOffsetMins > 0 then
1978     Result := Format('+%.2d:%.2d',[EffectiveTimeOffsetMins div 60,abs(EffectiveTimeOffsetMins mod 60)])
1979     else
1980     Result := Format('%.2d:%.2d',[EffectiveTimeOffsetMins div 60,abs(EffectiveTimeOffsetMins mod 60)]);
1981     end;
1982    
1983     function DecodeTimeZoneOffset(TZOffset: AnsiString; var dstOffset: integer): boolean;
1984     var i: integer;
1985     begin
1986     Result := false;
1987     TZOffset := Trim(TZOffset);
1988     for i := 1 to Length(TZOffset) do
1989     if not (TZOffset[i] in ['0'..'9','-','+',':']) then Exit;
1990    
1991     Result := true;
1992     i := Pos(':',TZOffset);
1993     if i > 0 then
1994     dstOffset := StrToInt(copy(TZOffset,1,i-1)) * 60 + StrToInt(copy(TZOffset,i + 1))
1995     else
1996     dstOffset := StrToInt(TZOffset) * 60;
1997     end;
1998    
1999     function StripLeadingZeros(Value: AnsiString): AnsiString;
2000     var i: Integer;
2001     start: integer;
2002     begin
2003     Result := '';
2004     start := 1;
2005     if (Length(Value) > 0) and (Value[1] = '-') then
2006     begin
2007     Result := '-';
2008     start := 2;
2009     end;
2010     for i := start to Length(Value) do
2011     if Value[i] <> '0' then
2012     begin
2013     Result := Result + system.copy(Value, i, MaxInt);
2014     Exit;
2015     end;
2016     end;
2017    
2018 tony 353 function TryStrToNumeric(S: Ansistring; out Value: int64; out scale: integer): boolean;
2019     var i: integer;
2020     ds: integer;
2021     exponent: integer;
2022     begin
2023     Result := false;
2024     ds := 0;
2025     exponent := 0;
2026     S := Trim(S);
2027     Value := 0;
2028     scale := 0;
2029     if Length(S) = 0 then
2030     Exit;
2031     {$IF declared(DefaultFormatSettings)}
2032     with DefaultFormatSettings do
2033     {$ELSE}
2034     {$IF declared(FormatSettings)}
2035     with FormatSettings do
2036     {$IFEND}
2037     {$IFEND}
2038     begin
2039     for i := length(S) downto 1 do
2040     begin
2041     if S[i] = AnsiChar(DecimalSeparator) then
2042     begin
2043     if ds <> 0 then Exit; {only one allowed}
2044 tony 356 ds := i;
2045 tony 353 dec(exponent);
2046     system.Delete(S,i,1);
2047     end
2048     else
2049 tony 354 if S[i] in ['+','-'] then
2050     begin
2051     if (i > 1) and not (S[i-1] in ['e','E']) then
2052     Exit; {malformed}
2053     end
2054 tony 353 else
2055     if S[i] in ['e','E'] then {scientific notation}
2056     begin
2057     if ds <> 0 then Exit; {not permitted in exponent}
2058     if exponent <> 0 then Exit; {only one allowed}
2059     exponent := i;
2060     end
2061     else
2062     if not (S[i] in ['0'..'9']) then
2063 tony 356 {Note: ThousandSeparator not allowed by Delphi specs}
2064 tony 353 Exit; {bad character}
2065     end;
2066    
2067     if exponent > 0 then
2068     begin
2069     Result := TryStrToInt(system.copy(S,exponent+1,maxint),Scale);
2070     if Result then
2071     begin
2072     {adjust scale for decimal point}
2073 tony 356 if ds <> 0 then
2074     Scale := Scale - (exponent - ds);
2075 tony 353 Result := TryStrToInt64(system.copy(S,1,exponent-1),Value);
2076     end;
2077     end
2078     else
2079     begin
2080     if ds <> 0 then
2081 tony 356 scale := ds - Length(S) - 1;
2082 tony 353 Result := TryStrToInt64(S,Value);
2083     end;
2084     end;
2085     end;
2086    
2087     function NumericToDouble(aValue: Int64; aScale: integer): double;
2088     begin
2089     Result := aValue * IntPower(10,aScale)
2090     end;
2091    
2092 tony 363
2093     function StringToHex(octetString: string; MaxLineLength: integer): string; overload;
2094    
2095     function ToHex(aValue: byte): string;
2096     const
2097     HexChars: array [0..15] of char = '0123456789ABCDEF';
2098     begin
2099     Result := HexChars[aValue shr 4] +
2100     HexChars[(aValue and $0F)];
2101     end;
2102    
2103     var i, j: integer;
2104     begin
2105     i := 1;
2106     Result := '';
2107     if MaxLineLength = 0 then
2108     while i <= Length(octetString) do
2109     begin
2110     Result := Result + ToHex(byte(octetString[i]));
2111     Inc(i);
2112     end
2113     else
2114     while i <= Length(octetString) do
2115     begin
2116     for j := 1 to MaxLineLength do
2117     begin
2118     if i > Length(octetString) then
2119     Exit
2120     else
2121     Result := Result + ToHex(byte(octetString[i]));
2122     inc(i);
2123     end;
2124     Result := Result + LineEnding;
2125     end;
2126     end;
2127    
2128     procedure StringToHex(octetString: string; TextOut: TStrings; MaxLineLength: integer); overload;
2129     begin
2130     TextOut.Add(StringToHex(octetString,MaxLineLength));
2131     end;
2132    
2133     { TSQLXMLReader }
2134    
2135     function TSQLXMLReader.FindTag(tag: string; var xmlTag: TXMLTag): boolean;
2136     var i: TXMLTag;
2137     begin
2138     Result := false;
2139     for i := xtBlob to xtElt do
2140     if XMLTagDefs[i].TagValue = tag then
2141     begin
2142     xmlTag := XMLTagDefs[i].XMLTag;
2143     Result := true;
2144     break;
2145     end;
2146     end;
2147    
2148     function TSQLXMLReader.GetArrayData(index: integer): TArrayData;
2149     begin
2150     if (index < 0) or (index > ArrayDataCount) then
2151     ShowError(sArrayIndexError,[index]);
2152     Result := FArrayData[index];
2153     end;
2154    
2155     function TSQLXMLReader.GetArrayDataCount: integer;
2156     begin
2157     Result := Length(FArrayData);
2158     end;
2159    
2160     function TSQLXMLReader.GetBlobData(index: integer): TBlobData;
2161     begin
2162     if (index < 0) or (index > BlobDataCount) then
2163     ShowError(sBlobIndexError,[index]);
2164     Result := FBlobData[index];
2165     end;
2166    
2167     function TSQLXMLReader.GetBlobDataCount: integer;
2168     begin
2169     Result := Length(FBlobData);
2170     end;
2171    
2172     function TSQLXMLReader.GetTagName(xmltag: TXMLTag): string;
2173     var i: TXMLTag;
2174     begin
2175     Result := 'unknown';
2176     for i := xtBlob to xtElt do
2177     if XMLTagDefs[i].XMLTag = xmltag then
2178     begin
2179     Result := XMLTagDefs[i].TagValue;
2180     Exit;
2181     end;
2182     end;
2183    
2184     procedure TSQLXMLReader.ProcessAttributeValue(attrValue: string);
2185     begin
2186     case FXMLTagStack[FXMLTagIndex] of
2187     xtBlob:
2188     if FAttributeName = 'subtype' then
2189     FBlobData[FCurrentBlob].SubType := StrToInt(attrValue)
2190     else
2191     ShowError(sXMLAttributeError,[FAttributeName,attrValue]);
2192    
2193     xtArray:
2194     if FAttributeName = 'sqltype' then
2195     FArrayData[FCurrentArray].SQLType := StrToInt(attrValue)
2196     else
2197     if FAttributeName = 'relation_name' then
2198     FArrayData[FCurrentArray].relationName := attrValue
2199     else
2200     if FAttributeName = 'column_name' then
2201     FArrayData[FCurrentArray].columnName := attrValue
2202     else
2203     if FAttributeName = 'dim' then
2204     FArrayData[FCurrentArray].Dim := StrToInt(attrValue)
2205     else
2206     if FAttributeName = 'length' then
2207     FArrayData[FCurrentArray].Size := StrToInt(attrValue)
2208     else
2209     if FAttributeName = 'scale' then
2210     FArrayData[FCurrentArray].Scale := StrToInt(attrValue)
2211     else
2212     if FAttributeName = 'charset' then
2213     FArrayData[FCurrentArray].CharSet := attrValue
2214     else
2215     if FAttributeName = 'bounds' then
2216     ProcessBoundsList(attrValue)
2217     else
2218     ShowError(sXMLAttributeError,[FAttributeName,attrValue]);
2219    
2220     xtElt:
2221     if FAttributeName = 'ix' then
2222     with FArrayData[FCurrentArray] do
2223     Index[CurrentRow] := StrToInt(attrValue)
2224     else
2225     ShowError(sXMLAttributeError,[FAttributeName,attrValue]);
2226     end;
2227     end;
2228    
2229     procedure TSQLXMLReader.ProcessBoundsList(boundsList: string);
2230     var list: TStringList;
2231     i,j: integer;
2232     begin
2233     list := TStringList.Create;
2234     try
2235     list.Delimiter := ',';
2236     list.DelimitedText := boundsList;
2237     with FArrayData[FCurrentArray] do
2238     begin
2239     if dim <> list.Count then
2240     ShowError(sInvalidBoundsList,[boundsList]);
2241     SetLength(bounds,dim);
2242     for i := 0 to list.Count - 1 do
2243     begin
2244     j := Pos(':',list[i]);
2245     if j = 0 then
2246     raise Exception.CreateFmt(sInvalidBoundsList,[boundsList]);
2247     bounds[i].LowerBound := StrToInt(system.copy(list[i],1,j-1));
2248     bounds[i].UpperBound := StrToInt(system.copy(list[i],j+1,length(list[i])-j));
2249     end;
2250     end;
2251     finally
2252     list.Free;
2253     end;
2254     end;
2255    
2256     procedure TSQLXMLReader.ProcessTagValue(tagValue: string);
2257    
2258     function nibble(hex: char): byte;
2259     begin
2260     case hex of
2261     '0': Result := 0;
2262     '1': Result := 1;
2263     '2': Result := 2;
2264     '3': Result := 3;
2265     '4': Result := 4;
2266     '5': Result := 5;
2267     '6': Result := 6;
2268     '7': Result := 7;
2269     '8': Result := 8;
2270     '9': Result := 9;
2271     'a','A': Result := 10;
2272     'b','B': Result := 11;
2273     'c','C': Result := 12;
2274     'd','D': Result := 13;
2275     'e','E': Result := 14;
2276     'f','F': Result := 15;
2277     end;
2278     end;
2279    
2280     procedure RemoveWhiteSpace(var hexData: string);
2281     var i: integer;
2282     begin
2283     {Remove White Space}
2284     i := 1;
2285     while i <= length(hexData) do
2286     begin
2287     case hexData[i] of
2288     ' ',#9,#10,#13:
2289     begin
2290     if i < Length(hexData) then
2291     Move(hexData[i+1],hexData[i],Length(hexData)-i);
2292     SetLength(hexData,Length(hexData)-1);
2293     end;
2294     else
2295     Inc(i);
2296     end;
2297     end;
2298     end;
2299    
2300     procedure WriteToBlob(hexData: string);
2301     var i,j : integer;
2302     blength: integer;
2303     P: PByte;
2304     begin
2305     RemoveWhiteSpace(hexData);
2306     if odd(length(hexData)) then
2307     ShowError(sBinaryBlockMustbeEven,[nil]);
2308     blength := Length(hexData) div 2;
2309     ReallocMem(FBlobBuffer,blength);
2310     j := 1;
2311     P := FBlobBuffer;
2312     for i := 1 to blength do
2313     begin
2314     P^ := (nibble(hexData[j]) shl 4) or nibble(hexdata[j+1]);
2315     Inc(j,2);
2316     Inc(P);
2317     end;
2318     FBlobData[FCurrentBlob].BlobIntf.Write(FBlobBuffer^,blength);
2319     end;
2320    
2321     begin
2322     if tagValue = '' then Exit;
2323     case FXMLTagStack[FXMLTagIndex] of
2324     xtBlob:
2325     WriteToBlob(tagValue);
2326    
2327     xtElt:
2328     with FArrayData[FCurrentArray] do
2329     ArrayIntf.SetAsString(index,tagValue);
2330    
2331     end;
2332     end;
2333    
2334     procedure TSQLXMLReader.XMLTagInit(xmltag: TXMLTag);
2335     begin
2336     if FXMLTagIndex > MaxXMLTags then
2337     ShowError(sXMLStackOverFlow,[nil]);
2338     Inc(FXMLTagIndex);
2339     FXMLTagStack[FXMLTagIndex] := xmltag;
2340     FXMLString := '';
2341    
2342     case xmltag of
2343     xtBlob:
2344     begin
2345     Inc(FCurrentBlob);
2346     SetLength(FBlobData,FCurrentBlob+1);
2347     FBlobData[FCurrentBlob].BlobIntf := nil;
2348     FBlobData[FCurrentBlob].SubType := 0;
2349     end;
2350    
2351     xtArray:
2352     begin
2353     Inc(FCurrentArray);
2354     SetLength(FArrayData,FCurrentArray+1);
2355     with FArrayData[FCurrentArray] do
2356     begin
2357     ArrayIntf := nil;
2358     SQLType := 0;
2359     dim := 0;
2360     Size := 0;
2361     Scale := 0;
2362     CharSet := 'NONE';
2363     SetLength(Index,0);
2364     CurrentRow := -1;
2365     end;
2366     end;
2367    
2368     xtElt:
2369     with FArrayData[FCurrentArray] do
2370     Inc(CurrentRow)
2371     end;
2372     end;
2373    
2374     function TSQLXMLReader.XMLTagEnd(var xmltag: TXMLTag): boolean;
2375     begin
2376     if FXMLTagIndex = 0 then
2377     ShowError(sXMLStackUnderflow,[nil]);
2378    
2379     xmlTag := FXMLTagStack[FXMLTagIndex];
2380     case FXMLTagStack[FXMLTagIndex] of
2381     xtBlob:
2382     FBlobData[FCurrentBlob].BlobIntf.Close;
2383    
2384     xtArray:
2385     FArrayData[FCurrentArray].ArrayIntf.SaveChanges;
2386    
2387     xtElt:
2388     Dec(FArrayData[FCurrentArray].CurrentRow);
2389     end;
2390     Dec(FXMLTagIndex);
2391     Result := FXMLTagIndex = 0;
2392     end;
2393    
2394     procedure TSQLXMLReader.XMLTagEnter;
2395     var aCharSetID: integer;
2396     begin
2397     if (Attachment = nil) or not Attachment.IsConnected then
2398     ShowError(sNoDatabase);
2399     if Transaction = nil then
2400     ShowError(sNoTransaction);
2401     case FXMLTagStack[FXMLTagIndex] of
2402     xtBlob:
2403     begin
2404     if not Transaction.InTransaction then
2405     Transaction.Start;
2406     FBlobData[FCurrentBlob].BlobIntf := Attachment.CreateBlob(
2407     Transaction,FBlobData[FCurrentBlob].SubType);
2408     end;
2409    
2410     xtArray:
2411     with FArrayData[FCurrentArray] do
2412     begin
2413     if not Transaction.InTransaction then
2414     Transaction.Start;
2415     Attachment.CharSetName2CharSetID(CharSet,aCharSetID);
2416     SetLength(Index,dim);
2417     ArrayIntf := Attachment.CreateArray(
2418     Transaction,
2419     Attachment.CreateArrayMetaData(SQLType,
2420     relationName,columnName,Scale,Size,
2421     aCharSetID,dim,bounds)
2422     );
2423     end;
2424     end;
2425     end;
2426    
2427     {This is where the XML tags are identified and the token stream modified in
2428     consequence}
2429    
2430     function TSQLXMLReader.TokenFound(var token: TSQLTokens): boolean;
2431    
2432     procedure NotAnXMLTag;
2433     begin
2434     begin
2435     if FXMLTagIndex = 0 then
2436     {nothing to do with XML so go back to processing SQL}
2437     begin
2438     QueueToken(token);
2439     ReleaseQueue(token);
2440     FXMLState := stNoXML
2441     end
2442     else
2443     begin
2444     {Not an XML tag, so just push back to XML Data}
2445     FXMLState := stXMLData;
2446     FXMLString := FXMLString + GetQueuedText;
2447     ResetQueue;
2448     end;
2449     end;
2450     end;
2451    
2452     var XMLTag: TXMLTag;
2453     begin
2454     Result := inherited TokenFound(token);
2455     if not Result then Exit;
2456    
2457     case FXMLState of
2458     stNoXML:
2459     if token = sqltLT then
2460     begin
2461     ResetQueue;
2462     QueueToken(token); {save in case this is not XML}
2463     FXMLState := stInTag;
2464     end;
2465    
2466     stInTag:
2467     {Opening '<' found, now looking for tag name or end tag marker}
2468     case token of
2469     sqltIdentifier:
2470     begin
2471     if FindTag(TokenText,XMLTag) then
2472     begin
2473     XMLTagInit(XMLTag);
2474     QueueToken(token);
2475     FXMLState := stInTagBody;
2476     end
2477     else
2478     NotAnXMLTag;
2479     end;
2480    
2481     sqltForwardSlash:
2482     FXMLState := stInEndTag;
2483    
2484     else
2485     NotAnXMLTag;
2486     end {case token};
2487    
2488     stInTagBody:
2489     {Tag name found. Now looking for attribute or closing '>'}
2490     case token of
2491     sqltIdentifier:
2492     begin
2493     FAttributeName := TokenText;
2494     QueueToken(token);
2495     FXMLState := stAttribute;
2496     end;
2497    
2498     sqltGT:
2499     begin
2500     ResetQueue;
2501     XMLTagEnter;
2502     FXMLState := stXMLData;
2503     end;
2504    
2505     sqltSpace,
2506     sqltEOL:
2507     QueueToken(token);
2508    
2509     else
2510     NotAnXMLTag;
2511     end {case token};
2512    
2513     stAttribute:
2514     {Attribute name found. Must be followed by an '=', a '>' or another tag name}
2515     case token of
2516     sqltEquals:
2517     begin
2518     QueueToken(token);
2519     FXMLState := stAttributeValue;
2520     end;
2521    
2522     sqltSpace,
2523     sqltEOL:
2524     QueueToken(token);
2525    
2526     sqltIdentifier:
2527     begin
2528     ProcessAttributeValue('');
2529     FAttributeName := TokenText;
2530     QueueToken(token);
2531     FXMLState := stAttribute;
2532     end;
2533    
2534     sqltGT:
2535     begin
2536     ProcessAttributeValue('');
2537     ResetQueue;
2538     XMLTagEnter;
2539     FXMLState := stXMLData;
2540     end;
2541    
2542     else
2543     NotAnXMLTag;
2544     end; {case token}
2545    
2546     stAttributeValue:
2547     {Looking for attribute value as a single identifier or a double quoted value}
2548     case token of
2549     sqltIdentifier,sqltIdentifierInDoubleQuotes:
2550     begin
2551     ProcessAttributeValue(TokenText);
2552     QueueToken(token);
2553     FXMLState := stInTagBody;
2554     end;
2555    
2556     sqltSpace,
2557     sqltEOL:
2558     QueueToken(token);
2559    
2560     else
2561     NotAnXMLTag;
2562     end; {case token}
2563    
2564     stXMLData:
2565     if token = sqltLT then
2566     begin
2567     QueueToken(token); {save in case this is not XML}
2568     FXMLState := stInTag;
2569     end
2570     else
2571     FXMLString := FXMLString + TokenText;
2572    
2573     stInEndTag:
2574     {Opening '</' found, now looking for tag name}
2575     case token of
2576     sqltIdentifier:
2577     begin
2578     if FindTag(TokenText,XMLTag) and (XMLTag = FXMLTagStack[FXMLTagIndex]) then
2579     begin
2580     QueueToken(token);
2581     FXMLState := stInEndTagBody;
2582     end
2583     else
2584     ShowError(sInvalidEndTag,[TokenText]);
2585     end;
2586     else
2587     NotAnXMLTag;
2588     end {case token};
2589    
2590     stInEndTagBody:
2591     {End tag name found, now looping for closing '>'}
2592     case Token of
2593     sqltGT:
2594     begin
2595     ProcessTagValue(FXMLString);
2596     if XMLTagEnd(XMLTag) then
2597     begin
2598     ResetQueue;
2599     QueueToken(sqltColon,':');
2600     case XMLTag of
2601     xtBlob:
2602     QueueToken(sqltIdentifier,Format(ibx_blob+'%d',[FCurrentBlob]));
2603    
2604     xtArray:
2605     QueueToken(sqltIdentifier, Format(ibx_array+'%d',[FCurrentArray]));
2606     end;
2607     ReleaseQueue(token);
2608     FXMLState := stNoXML;
2609     end
2610     else
2611     FXMLState := stXMLData;
2612     end;
2613    
2614     sqltSpace,
2615     sqltEOL:
2616     QueueToken(token);
2617    
2618     else
2619     ShowError(sBadEndTagClosing);
2620     end; {case token}
2621    
2622     end {Case FState};
2623    
2624     {Only allow token to be returned if not processing an XML tag}
2625    
2626     Result := FXMLState = stNoXML;
2627     end;
2628    
2629     procedure TSQLXMLReader.ShowError(msg: string; params: array of const);
2630     begin
2631     raise EIBClientError.CreateFmt(GetErrorPrefix + msg,params);
2632     end;
2633    
2634     procedure TSQLXMLReader.ShowError(msg: string);
2635     begin
2636     ShowError(msg,[nil]);
2637     end;
2638    
2639     constructor TSQLXMLReader.Create;
2640     begin
2641     inherited;
2642     FXMLState := stNoXML;
2643     end;
2644    
2645     procedure TSQLXMLReader.FreeDataObjects;
2646     begin
2647     FXMLTagIndex := 0;
2648     SetLength(FBlobData,0);
2649     FCurrentBlob := -1;
2650     SetLength(FArrayData,0);
2651     FCurrentArray := -1;
2652     end;
2653    
2654     class function TSQLXMLReader.FormatBlob(Field: ISQLData): string;
2655     begin
2656     Result := FormatBlob(Field.AsString,Field.getSubtype);
2657     end;
2658    
2659     class function TSQLXMLReader.FormatBlob(contents: string; subtype: integer
2660     ): string;
2661     var TextOut: TStrings;
2662     begin
2663     TextOut := TStringList.Create;
2664     try
2665     TextOut.Add(Format('<blob subtype="%d">',[subtype]));
2666     StringToHex(contents,TextOut,BlobLineLength);
2667     TextOut.Add('</blob>');
2668     Result := TextOut.Text;
2669     finally
2670     TextOut.Free;
2671     end;
2672     end;
2673    
2674    
2675     class function TSQLXMLReader.FormatArray(ar: IArray
2676     ): string;
2677     var index: array of integer;
2678     TextOut: TStrings;
2679    
2680     procedure AddElements(dim: integer; indent:string = ' ');
2681     var i: integer;
2682     recurse: boolean;
2683     begin
2684     SetLength(index,dim+1);
2685     recurse := dim < ar.GetDimensions - 1;
2686     with ar.GetBounds[dim] do
2687     for i := LowerBound to UpperBound do
2688     begin
2689     index[dim] := i;
2690     if recurse then
2691     begin
2692     TextOut.Add(Format('%s<elt id="%d">',[indent,i]));
2693     AddElements(dim+1,indent + ' ');
2694     TextOut.Add('</elt>');
2695     end
2696     else
2697     if ((ar.GetSQLType = SQL_TEXT) or (ar.GetSQLType = SQL_VARYING)) and
2698     (ar.GetCharSetID = 1) then
2699     TextOut.Add(Format('%s<elt ix="%d">%s</elt>',[indent,i,StringToHex(ar.GetAsString(index))]))
2700     else
2701     TextOut.Add(Format('%s<elt ix="%d">%s</elt>',[indent,i,ar.GetAsString(index)]));
2702     end;
2703     end;
2704    
2705     var
2706     s: string;
2707     bounds: TArrayBounds;
2708     i: integer;
2709     boundsList: string;
2710     begin
2711     TextOut := TStringList.Create;
2712     try
2713     if ar.GetCharSetWidth = 0 then
2714     s := Format('<array dim = "%d" sqltype = "%d" length = "%d" relation_name = "%s" column_name = "%s"',
2715     [ar.GetDimensions,ar.GetSQLType,ar.GetSize,
2716     ar.GetTableName,ar.GetColumnName])
2717     else
2718     s := Format('<array dim = "%d" sqltype = "%d" length = "%d" relation_name = "%s" column_name = "%s"',
2719     [ar.GetDimensions,ar.GetSQLType,ar.GetSize div ar.GetCharSetWidth,
2720     ar.GetTableName,ar.GetColumnName]);
2721     case ar.GetSQLType of
2722     SQL_DOUBLE, SQL_FLOAT, SQL_LONG, SQL_SHORT, SQL_D_FLOAT, SQL_INT64:
2723     s := s + Format(' scale = "%d"',[ ar.GetScale]);
2724     SQL_TEXT,
2725     SQL_VARYING:
2726     s := s + Format(' charset = "%s"',[ar.GetAttachment.GetCharsetName(ar.GetCharSetID)]);
2727     end;
2728     bounds := ar.GetBounds;
2729     boundsList := '';
2730     for i := 0 to length(bounds) - 1 do
2731     begin
2732     if i <> 0 then boundsList := boundsList + ',';
2733     boundsList := boundsList + Format('%d:%d',[bounds[i].LowerBound,bounds[i].UpperBound]);
2734     end;
2735     s := s + Format(' bounds="%s"',[boundsList]);
2736     s := s + '>';
2737     TextOut.Add(s);
2738    
2739     SetLength(index,0);
2740     AddElements(0);
2741     TextOut.Add('</array>');
2742     Result := TextOut.Text;
2743     finally
2744     TextOut.Free;
2745     end;
2746     end;
2747    
2748     procedure TSQLXMLReader.Reset;
2749     begin
2750     inherited Reset;
2751     FreeDataObjects;
2752     FXMLString := '';
2753     FreeMem(FBlobBuffer);
2754     end;
2755    
2756     { TJournalProcessor }
2757    
2758     procedure TJournalProcessor.DoExecute;
2759     var token: TSQLTokens;
2760     LineState: TLineState;
2761     JnlEntry: TJnlEntry;
2762     Len: integer;
2763     tz: AnsiString;
2764    
2765     procedure ClearJnlEntry;
2766     begin
2767     with JnlEntry do
2768     begin
2769     TransactionName := '';
2770     TPB := nil;
2771     QueryText :='';
2772     JnlEntryType := jeUnknown;
2773     SessionID := 0;
2774     TransactionID := 0;
2775     DefaultCompletion := taCommit;
2776     end;
2777     end;
2778    
2779     function CreateTPB(TPBText: AnsiString): ITPB;
2780     var index: integer;
2781     begin
2782     Result := nil;
2783     if Length(TPBText) = 0 then
2784     Exit;
2785     Result := FFirebirdClientAPI.AllocateTPB;
2786     try
2787     index := Pos('[',TPBText);
2788     if index > 0 then
2789     system.Delete(TPBText,1,index);
2790     repeat
2791     index := Pos(',',TPBText);
2792     if index = 0 then
2793     begin
2794     index := Pos(']',TPBText);
2795     if index <> 0 then
2796     system.Delete(TPBText,index,1);
2797     Result.AddByTypeName(TPBText);
2798     break;
2799     end;
2800     Result.AddByTypeName(system.copy(TPBText,1,index-1));
2801     system.Delete(TPBText,1,index);
2802     until false;
2803     except
2804     Result := nil;
2805     raise;
2806     end;
2807     end;
2808    
2809     begin
2810     LineState := lsInit;
2811     JnlEntry.JnlEntryType := jeUnknown;
2812     while not EOF do
2813     begin
2814     if LineState = lsInit then
2815     ClearJnlEntry;
2816     token := GetNextToken;
2817     with JnlEntry do
2818     case token of
2819     sqltAsterisk:
2820     if LineState = lsInit then
2821     LineState := lsJnlFound;
2822    
2823     sqltIdentifier:
2824     if LineState = lsJnlFound then
2825     begin
2826     JnlEntryType := IdentifyJnlEntry(TokenText);
2827     LineState := lsGotJnlType;
2828     end
2829     else
2830     LineState := lsInit;
2831    
2832     sqltQuotedString:
2833     if (LineState = lsGotJnlType)
2834     and ParseDateTimeTZString(TokenText,TimeStamp,tz) then
2835     LineState := lsGotTimestamp
2836     else
2837     LineState := lsInit;
2838    
2839     sqltColon:
2840     case LineState of
2841     lsGotText1Length:
2842     begin
2843     if Len > 0 then
2844     begin
2845     if JnlEntryType = jeTransStart then
2846     TransactionName := ReadCharacters(Len)
2847     else
2848     QueryText := ReadCharacters(Len)
2849     end;
2850     if JnlEntryType = jeTransStart then
2851     LineState := lsGotText1
2852     else
2853     begin
2854     if assigned(FOnNextJournalEntry) then
2855     OnNextJournalEntry(JnlEntry);
2856     LineState := lsInit;
2857     end
2858     end;
2859    
2860     lsGotText2Length:
2861     begin
2862     if Len > 0 then
2863     TPB := CreateTPB(ReadCharacters(Len));
2864     LineState := lsGotText2;
2865     end;
2866    
2867     else
2868     if LineState <> lsGotJnlType then
2869     LineState := lsInit;
2870     end;
2871    
2872     sqltComma:
2873     if not (LineState in [lsGotTimestamp,lsGotSessionID,lsGotTransactionID,lsGotText1,lsGotText2]) then
2874     LineState := lsInit;
2875    
2876     sqltNumberString:
2877     case LineState of
2878     lsGotTimestamp:
2879     begin
2880     SessionID := StrToInt(TokenText);
2881     LineState := lsGotSessionID;
2882     end;
2883    
2884     lsGotSessionID:
2885     begin
2886     TransactionID := StrToInt(TokenText);
2887     if JnlEntryType in [jeTransCommit, jeTransRollback] then
2888     begin
2889     if assigned(FOnNextJournalEntry) then
2890     OnNextJournalEntry(JnlEntry);
2891     LineState := lsInit;
2892     end
2893     else
2894     LineState := lsGotTransactionID;
2895     end;
2896    
2897     lsGotTransactionID:
2898     begin
2899     case JnlEntryType of
2900     jeTransStart:
2901     begin
2902     len := StrToInt(TokenText);
2903     LineState := lsGotText1Length;
2904     end;
2905    
2906     jeQuery:
2907     begin
2908     len := StrToInt(TokenText);
2909     LineState := lsGotText1Length;
2910     end;
2911    
2912     jeTransCommitRet,
2913     jeTransRollbackRet:
2914     begin
2915     OldTransactionID := StrToInt(TokenText);
2916     if assigned(FOnNextJournalEntry) then
2917     OnNextJournalEntry(JnlEntry);
2918     LineState := lsInit;
2919     end;
2920    
2921     else
2922     LineState := lsInit;
2923     end; {case JnlEntryType}
2924    
2925     end;
2926    
2927     lsGotText1:
2928     begin
2929     len := StrToInt(TokenText);
2930     LineState := lsGotText2Length;
2931     end;
2932    
2933     lsGotText2:
2934     begin
2935     if JnlEntryType = jeTransStart then
2936     begin
2937     DefaultCompletion := TTransactionCompletion(StrToInt(TokenText));
2938     if assigned(FOnNextJournalEntry) then
2939     OnNextJournalEntry(JnlEntry);
2940     end;
2941     LineState := lsInit;
2942     end;
2943     end; {case LineState}
2944     end; {case token}
2945     end; {while}
2946     ClearJnlEntry;
2947     end;
2948    
2949     function TJournalProcessor.IdentifyJnlEntry(aTokenText: AnsiString
2950     ): TJnlEntryType;
2951     begin
2952     Result := jeUnknown;
2953     if Length(aTokenText) > 0 then
2954     case aTokenText[1] of
2955     'S':
2956     Result := jeTransStart;
2957     'C':
2958     Result := jeTransCommit;
2959     'c':
2960     Result := jeTransCommitRet;
2961     'R':
2962     Result := jeTransRollback;
2963     'r':
2964     Result := jeTransRollbackRet;
2965     'E':
2966     Result := jeTransEnd;
2967     'Q':
2968     Result := jeQuery;
2969     end;
2970     end;
2971    
2972     class function TJournalProcessor.JnlEntryText(je: TJnlEntryType): string;
2973     begin
2974     case je of
2975     jeTransStart:
2976     Result := 'Transaction Start';
2977     jeTransCommit:
2978     Result := 'Commit';
2979     jeTransCommitRet:
2980     Result := 'Commit Retaining';
2981     jeTransRollback:
2982     Result := 'Rollback';
2983     jeTransRollbackRet:
2984     Result := 'Rollback Retaining';
2985     jeTransEnd:
2986     Result := 'Transaction End';
2987     jeQuery:
2988     Result := 'Query';
2989     jeUnknown:
2990     Result := 'Unknown';
2991     end;
2992     end;
2993    
2994     function TJournalProcessor.GetChar: AnsiChar;
2995     begin
2996     if FInStream.Read(Result,1) = 0 then
2997     Result := #0;
2998     end;
2999    
3000     destructor TJournalProcessor.Destroy;
3001     begin
3002     FInStream.Free;
3003     inherited Destroy;
3004     end;
3005    
3006     class procedure TJournalProcessor.Execute(aFileName: string; api: IFirebirdAPI;
3007     aOnNextJournalEntry: TOnNextJournalEntry);
3008     begin
3009     with TJournalProcessor.Create do
3010     try
3011     FInStream := TFileStream.Create(aFileName,fmOpenRead);
3012     FFirebirdClientAPI := api;
3013     OnNextJournalEntry := aOnNextJournalEntry;
3014     DoExecute;
3015     finally
3016     Free
3017     end;
3018     end;
3019    
3020    
3021 tony 45 end.