ViewVC Help
View File | Revision Log | Show Annotations | Download File | View Changeset | Root Listing
root/public/ibx/branches/journaling/fbintf/IBUtils.pas
Revision: 348
Committed: Wed Oct 6 09:38:14 2021 UTC (2 years, 6 months ago) by tony
Content type: text/x-pascal
Original Path: ibx/trunk/fbintf/IBUtils.pas
File size: 44457 byte(s)
Log Message:
Fixes Merged

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 263 type
53     TSQLTokens = (
54    
55     {Reserved Words}
56    
57     sqltAdd,
58     sqltAdmin,
59     sqltAll,
60     sqltAlter,
61     sqltAnd,
62     sqltAny,
63     sqltAs,
64     sqltAt,
65     sqltAvg,
66     sqltBegin,
67     sqltBetween,
68     sqltBigint,
69     sqltBit_Length,
70     sqltBlob,
71     sqltBoolean,
72     sqltBoth,
73     sqltBy,
74     sqltCase,
75     sqltCast,
76     sqltChar,
77     sqltChar_Length,
78     sqltCharacter,
79     sqltCharacter_Length,
80     sqltCheck,
81     sqltClose,
82     sqltCollate,
83     sqltColumn,
84     sqltCommit,
85     sqltConnect,
86     sqltConstraint,
87     sqltCorr,
88     sqltCount,
89     sqltCovar_Pop,
90     sqltCovar_Samp,
91     sqltCreate,
92     sqltCross,
93     sqltCurrent,
94     sqltCurrent_Connection,
95     sqltCurrent_Date,
96     sqltCurrent_Role,
97     sqltCurrent_Time,
98     sqltCurrent_Timestamp,
99     sqltCurrent_Transaction,
100     sqltCurrent_User,
101     sqltCursor,
102     sqltDate,
103     sqltDay,
104     sqltDec,
105     sqltDecimal,
106     sqltDeclare,
107     sqltDefault,
108     sqltDelete,
109     sqltDeleting,
110     sqltDeterministic,
111     sqltDisconnect,
112     sqltDistinct,
113     sqltDouble,
114     sqltDrop,
115     sqltElse,
116     sqltEnd,
117     sqltEscape,
118     sqltExecute,
119     sqltExists,
120     sqltExternal,
121     sqltExtract,
122     sqltFalse,
123     sqltFetch,
124     sqltFilter,
125     sqltFloat,
126     sqltFor,
127     sqltForeign,
128     sqltFrom,
129     sqltFull,
130     sqltFunction,
131     sqltGdscode,
132     sqltGlobal,
133     sqltGrant,
134     sqltGroup,
135     sqltHaving,
136     sqltHour,
137     sqltIn,
138     sqltIndex,
139     sqltInner,
140     sqltInsensitive,
141     sqltInsert,
142     sqltInserting,
143     sqltInt,
144     sqltInteger,
145     sqltInto,
146     sqltIs,
147     sqltJoin,
148     sqltKey,
149     sqltLeading,
150     sqltLeft,
151     sqltLike,
152     sqltLong,
153     sqltLower,
154     sqltMax,
155     sqltMaximum_Segment,
156     sqltMerge,
157     sqltMin,
158     sqltMinute,
159     sqltMonth,
160     sqltNational,
161     sqltNatural,
162     sqltNchar,
163     sqltNo,
164     sqltNot,
165     sqltNull,
166     sqltNumeric,
167     sqltOctet_Length,
168     sqltOf,
169     sqltOffset,
170     sqltOn,
171     sqltOnly,
172     sqltOpen,
173     sqltOr,
174     sqltOrder,
175     sqltOuter,
176     sqltOver,
177     sqltParameter,
178     sqltPlan,
179     sqltPosition,
180     sqltPost_Event,
181     sqltPrecision,
182     sqltPrimary,
183     sqltProcedure,
184     sqltRdbDb_Key,
185     sqltRdbRecord_Version,
186     sqltReal,
187     sqltRecord_Version,
188     sqltRecreate,
189     sqltRecursive,
190     sqltReferences,
191     sqltRegr_Avgx,
192     sqltRegr_Avgy,
193     sqltRegr_Count,
194     sqltRegr_Intercept,
195     sqltRegr_R2,
196     sqltRegr_Slope,
197     sqltRegr_Sxx,
198     sqltRegr_Sxy,
199     sqltRegr_Syy,
200     sqltRelease,
201     sqltReturn,
202     sqltReturning_Values,
203     sqltReturns,
204     sqltRevoke,
205     sqltRight,
206     sqltRollback,
207     sqltRow,
208     sqltRows,
209     sqltRow_Count,
210     sqltSavepoint,
211     sqltScroll,
212     sqltSecond,
213     sqltSelect,
214     sqltSensitive,
215     sqltSet,
216     sqltSimilar,
217     sqltSmallint,
218     sqltSome,
219     sqltSqlcode,
220     sqltSqlstate,
221     sqltStart,
222     sqltStddev_Pop,
223     sqltStddev_Samp,
224     sqltSum,
225     sqltTable,
226     sqltThen,
227     sqltTime,
228     sqltTimestamp,
229     sqltTo,
230     sqltTrailing,
231     sqltTrigger,
232     sqltTrim,
233     sqltTrue,
234     sqltUnion,
235     sqltUnique,
236     sqltUnknown,
237     sqltUpdate,
238     sqltUpdating,
239     sqltUpper,
240     sqltUser,
241     sqltUsing,
242     sqltValue,
243     sqltValues,
244     sqltVar_Pop,
245     sqltVar_Samp,
246     sqltVarchar,
247     sqltVariable,
248     sqltVarying,
249     sqltView,
250     sqltWhen,
251     sqltWhere,
252     sqltWhile,
253     sqltWith,
254     sqltYear,
255    
256     {symbols}
257    
258     sqltSpace,
259     sqltSemiColon,
260     sqltPlaceholder,
261     sqltSingleQuotes,
262     sqltDoubleQuotes,
263 tony 270 sqltBackslash,
264 tony 263 sqltComma,
265     sqltPeriod,
266     sqltEquals,
267     sqltOtherCharacter,
268     sqltIdentifier,
269     sqltIdentifierInDoubleQuotes,
270     sqltNumberString,
271     sqltString,
272     sqltParam,
273     sqltQuotedParam,
274     sqltColon,
275     sqltComment,
276     sqltCommentLine,
277     sqltQuotedString,
278     sqltAsterisk,
279     sqltForwardSlash,
280     sqltOpenSquareBracket,
281     sqltCloseSquareBracket,
282     sqltOpenBracket,
283     sqltCloseBracket,
284     sqltPipe,
285 tony 287 sqltMinus,
286 tony 263 sqltConcatSymbol,
287     sqltLT,
288     sqltGT,
289     sqltCR,
290     sqltEOL,
291     sqltEOF,
292     sqltInit
293     );
294    
295     TSQLReservedWords = sqltAdd..sqltYear;
296    
297 tony 45 const
298     CRLF = #13 + #10;
299     CR = #13;
300     LF = #10;
301     TAB = #9;
302     NULL_TERMINATOR = #0;
303    
304 tony 263 {$IFNDEF FPC}
305     LineEnding = CRLF;
306     {$ENDIF}
307    
308     {SQL Reserved words in alphabetical order}
309    
310     sqlReservedWords: array [TSQLReservedWords] of string = (
311 tony 47 'ADD',
312     'ADMIN',
313     'ALL',
314     'ALTER',
315     'AND',
316     'ANY',
317     'AS',
318     'AT',
319     'AVG',
320     'BEGIN',
321     'BETWEEN',
322     'BIGINT',
323     'BIT_LENGTH',
324     'BLOB',
325     'BOOLEAN',
326     'BOTH',
327     'BY',
328     'CASE',
329     'CAST',
330     'CHAR',
331     'CHAR_LENGTH',
332     'CHARACTER',
333     'CHARACTER_LENGTH',
334     'CHECK',
335     'CLOSE',
336     'COLLATE',
337     'COLUMN',
338     'COMMIT',
339     'CONNECT',
340     'CONSTRAINT',
341     'CORR',
342     'COUNT',
343     'COVAR_POP',
344     'COVAR_SAMP',
345     'CREATE',
346     'CROSS',
347     'CURRENT',
348     'CURRENT_CONNECTION',
349     'CURRENT_DATE',
350     'CURRENT_ROLE',
351     'CURRENT_TIME',
352     'CURRENT_TIMESTAMP',
353     'CURRENT_TRANSACTION',
354     'CURRENT_USER',
355     'CURSOR',
356     'DATE',
357     'DAY',
358     'DEC',
359     'DECIMAL',
360     'DECLARE',
361     'DEFAULT',
362     'DELETE',
363     'DELETING',
364     'DETERMINISTIC',
365     'DISCONNECT',
366     'DISTINCT',
367     'DOUBLE',
368     'DROP',
369     'ELSE',
370     'END',
371     'ESCAPE',
372     'EXECUTE',
373     'EXISTS',
374     'EXTERNAL',
375     'EXTRACT',
376     'FALSE',
377     'FETCH',
378     'FILTER',
379     'FLOAT',
380     'FOR',
381     'FOREIGN',
382     'FROM',
383     'FULL',
384     'FUNCTION',
385     'GDSCODE',
386     'GLOBAL',
387     'GRANT',
388     'GROUP',
389     'HAVING',
390     'HOUR',
391     'IN',
392     'INDEX',
393     'INNER',
394     'INSENSITIVE',
395     'INSERT',
396     'INSERTING',
397     'INT',
398     'INTEGER',
399     'INTO',
400     'IS',
401     'JOIN',
402 tony 209 'KEY',
403 tony 47 'LEADING',
404     'LEFT',
405     'LIKE',
406     'LONG',
407     'LOWER',
408     'MAX',
409     'MAXIMUM_SEGMENT',
410     'MERGE',
411     'MIN',
412     'MINUTE',
413     'MONTH',
414     'NATIONAL',
415     'NATURAL',
416     'NCHAR',
417     'NO',
418     'NOT',
419     'NULL',
420     'NUMERIC',
421     'OCTET_LENGTH',
422     'OF',
423     'OFFSET',
424     'ON',
425     'ONLY',
426     'OPEN',
427     'OR',
428     'ORDER',
429     'OUTER',
430     'OVER',
431     'PARAMETER',
432     'PLAN',
433     'POSITION',
434     'POST_EVENT',
435     'PRECISION',
436     'PRIMARY',
437     'PROCEDURE',
438     'RDB$DB_KEY',
439     'RDB$RECORD_VERSION',
440     'REAL',
441     'RECORD_VERSION',
442     'RECREATE',
443     'RECURSIVE',
444     'REFERENCES',
445     'REGR_AVGX',
446     'REGR_AVGY',
447     'REGR_COUNT',
448     'REGR_INTERCEPT',
449     'REGR_R2',
450     'REGR_SLOPE',
451     'REGR_SXX',
452     'REGR_SXY',
453     'REGR_SYY',
454     'RELEASE',
455     'RETURN',
456     'RETURNING_VALUES',
457     'RETURNS',
458     'REVOKE',
459     'RIGHT',
460     'ROLLBACK',
461     'ROW',
462 tony 263 'ROWS',
463 tony 47 'ROW_COUNT',
464     'SAVEPOINT',
465     'SCROLL',
466     'SECOND',
467     'SELECT',
468     'SENSITIVE',
469     'SET',
470     'SIMILAR',
471     'SMALLINT',
472     'SOME',
473     'SQLCODE',
474     'SQLSTATE',
475     'START',
476     'STDDEV_POP',
477     'STDDEV_SAMP',
478     'SUM',
479     'TABLE',
480     'THEN',
481     'TIME',
482     'TIMESTAMP',
483     'TO',
484     'TRAILING',
485     'TRIGGER',
486     'TRIM',
487     'TRUE',
488     'UNION',
489     'UNIQUE',
490     'UNKNOWN',
491     'UPDATE',
492     'UPDATING',
493     'UPPER',
494     'USER',
495     'USING',
496     'VALUE',
497     'VALUES',
498     'VAR_POP',
499     'VAR_SAMP',
500     'VARCHAR',
501     'VARIABLE',
502     'VARYING',
503     'VIEW',
504     'WHEN',
505     'WHERE',
506     'WHILE',
507     'WITH',
508     'YEAR'
509     );
510 tony 45
511 tony 263 type
512     {The TSQLTokeniser class provides a common means to parse an SQL statement, or
513     even a stream of SQL Statements. The TSQLStringTokeniser class is instantiated
514     with a single SQL statement or a set of concatenated statements. The TSQLStreamTokeniser
515     is instantiated with a stream from which the SQL statements are read.
516    
517     Successive calls to GetNextToken then return each SQL token. The TokenText contains
518     either the single character, the identifier or reserved word, the string or comment.}
519    
520     { TSQLTokeniser }
521    
522     TSQLTokeniser = class
523     private
524     const
525     TokenQueueMaxSize = 64;
526     type
527     TLexState = (stDefault, stInCommentLine, stInComment, stSingleQuoted, stDoubleQuoted,
528     stInIdentifier, stInNumeric);
529    
530     TTokenQueueItem = record
531     token: TSQLTokens;
532     text: AnsiString;
533     end;
534     TTokenQueueState = (tsHold, tsRelease);
535    
536     private
537     FLastChar: AnsiChar;
538     FState: TLexState;
539     FSkipNext: boolean;
540     function GetNext: TSQLTokens;
541    
542     {The token Queue is available for use by descendents so that they can
543     hold back tokens in order to lookahead by token rather than just a single
544     character}
545    
546     private
547     FTokenQueue: array[0..TokenQueueMaxSize] of TTokenQueueItem;
548     FQueueState: TTokenQueueState;
549     FQFirst: integer; {first and last pointers first=last => queue empty}
550     FQLast: integer;
551     FEOF: boolean;
552     procedure PopQueue(var token: TSQLTokens);
553     protected
554     FString: AnsiString;
555     FNextToken: TSQLTokens;
556     procedure Assign(source: TSQLTokeniser); virtual;
557     function GetChar: AnsiChar; virtual; abstract;
558     function TokenFound(var token: TSQLTokens): boolean; virtual;
559     function InternalGetNextToken: TSQLTokens; virtual;
560     procedure Reset; virtual;
561    
562     {Token stack}
563     procedure QueueToken(token: TSQLTokens; text:AnsiString); overload;
564     procedure QueueToken(token: TSQLTokens); overload;
565     procedure ResetQueue; overload;
566     procedure ResetQueue(token: TSQLTokens; text:AnsiString); overload;
567     procedure ResetQueue(token: TSQLTokens); overload;
568     procedure ReleaseQueue(var token: TSQLTokens); overload;
569     procedure ReleaseQueue; overload;
570     function GetQueuedText: AnsiString;
571     procedure SetTokenText(text: AnsiString);
572    
573     public
574     const
575     DefaultTerminator = ';';
576     public
577     constructor Create;
578     destructor Destroy; override;
579     function GetNextToken: TSQLTokens;
580     property EOF: boolean read FEOF;
581     property TokenText: AnsiString read FString;
582     end;
583    
584     { TSQLwithNamedParamsTokeniser }
585    
586     TSQLwithNamedParamsTokeniser = class(TSQLTokeniser)
587     private
588     type
589     TSQLState = (stInit,stInParam,stInBlock, stInArrayDim);
590     private
591     FState: TSQLState;
592     FNested: integer;
593     protected
594     procedure Assign(source: TSQLTokeniser); override;
595     procedure Reset; override;
596     function TokenFound(var token: TSQLTokens): boolean; override;
597     end;
598    
599 tony 270 { TSQLParamProcessor }
600    
601     TSQLParamProcessor = class(TSQLwithNamedParamsTokeniser)
602     private
603     const
604     sIBXParam = 'IBXParam'; {do not localize}
605     private
606     FInString: AnsiString;
607     FIndex: integer;
608     function DoExecute(GenerateParamNames: boolean;
609     var slNames: TStrings): AnsiString;
610     protected
611     function GetChar: AnsiChar; override;
612     public
613     class function Execute(sSQL: AnsiString; GenerateParamNames: boolean;
614     var slNames: TStrings): AnsiString;
615     end;
616    
617    
618 tony 45 function Max(n1, n2: Integer): Integer;
619     function Min(n1, n2: Integer): Integer;
620 tony 56 function RandomString(iLength: Integer): AnsiString;
621 tony 45 function RandomInteger(iLow, iHigh: Integer): Integer;
622 tony 56 function StripString(st: AnsiString; CharsToStrip: AnsiString): AnsiString;
623     function ExtractIdentifier(Dialect: Integer; Value: AnsiString): AnsiString;
624 tony 263 function FindReservedWord(w: AnsiString; var token: TSQLTokens): boolean;
625     function IsReservedWord(w: AnsiString): boolean;
626 tony 56 function QuoteIdentifier(Dialect: Integer; Value: AnsiString): AnsiString;
627     function QuoteIdentifierIfNeeded(Dialect: Integer; Value: AnsiString): AnsiString;
628     function Space2Underscore(s: AnsiString): AnsiString;
629     function SQLSafeString(const s: AnsiString): AnsiString;
630 tony 107 function IsSQLIdentifier(Value: AnsiString): boolean;
631 tony 117 function ExtractConnectString(const CreateSQL: AnsiString; var ConnectString: AnsiString): boolean;
632 tony 143 function MakeConnectString(ServerName, DatabaseName: AnsiString; Protocol: TProtocol;
633     PortNo: AnsiString = ''): AnsiString;
634     function ParseConnectString(ConnectString: AnsiString;
635     var ServerName, DatabaseName: AnsiString; var Protocol: TProtocolAll;
636     var PortNo: AnsiString): boolean;
637     function GetProtocol(ConnectString: AnsiString): TProtocolAll;
638 tony 45
639 tony 315 {$IF declared(TFormatSettings)}
640     function ParseDateTimeTZString(aDateTimeStr: Ansistring; var aDateTime: TDateTime;
641     var aTimezone: AnsiString; aFormatSettings: TFormatSettings; TimeOnly: boolean=false): boolean; overload;
642     {$IFEND}
643     function ParseDateTimeTZString(aDateTimeStr: Ansistring; var aDateTime: TDateTime;
644     var aTimezone: AnsiString; TimeOnly: boolean=false): boolean; overload;
645     procedure FBDecodeTime(aTime: TDateTime; var Hour, Minute, Second: word; var DeciMillisecond: cardinal);
646     function FBEncodeTime(Hour, Minute, Second, DeciMillisecond: cardinal): TDateTime;
647     function FBFormatDateTime(fmt: AnsiString; aDateTime: TDateTime): AnsiString;
648     function FormatTimeZoneOffset(EffectiveTimeOffsetMins: integer): AnsiString;
649     function DecodeTimeZoneOffset(TZOffset: AnsiString; var dstOffset: integer): boolean;
650     function StripLeadingZeros(Value: AnsiString): AnsiString;
651    
652 tony 45 implementation
653    
654 tony 263 uses FBMessages
655    
656 tony 315 {$IFDEF FPC}
657 tony 263 ,RegExpr
658 tony 315 {$ELSE}
659     {$IF declared(CompilerVersion) and (CompilerVersion >= 22)}
660     , RegularExpressions
661     {$IFEND}
662 tony 263 {$ENDIF};
663 tony 117
664 tony 315
665 tony 45 function Max(n1, n2: Integer): Integer;
666     begin
667     if (n1 > n2) then
668     result := n1
669     else
670     result := n2;
671     end;
672    
673     function Min(n1, n2: Integer): Integer;
674     begin
675     if (n1 < n2) then
676     result := n1
677     else
678     result := n2;
679     end;
680    
681 tony 56 function RandomString(iLength: Integer): AnsiString;
682 tony 45 begin
683     result := '';
684     while Length(result) < iLength do
685     result := result + IntToStr(RandomInteger(0, High(Integer)));
686     if Length(result) > iLength then
687     result := Copy(result, 1, iLength);
688     end;
689    
690     function RandomInteger(iLow, iHigh: Integer): Integer;
691     begin
692     result := Trunc(Random(iHigh - iLow)) + iLow;
693     end;
694    
695 tony 56 function StripString(st: AnsiString; CharsToStrip: AnsiString): AnsiString;
696 tony 45 var
697     i: Integer;
698     begin
699     result := '';
700     for i := 1 to Length(st) do begin
701     if AnsiPos(st[i], CharsToStrip) = 0 then
702     result := result + st[i];
703     end;
704     end;
705    
706 tony 117 {Extracts SQL Identifier typically from a Dialect 3 encoding}
707 tony 45
708 tony 117 function ExtractIdentifier(Dialect: Integer; Value: AnsiString): AnsiString;
709 tony 45 begin
710     Value := Trim(Value);
711     if Dialect = 1 then
712     Value := AnsiUpperCase(Value)
713     else
714     begin
715     if (Value <> '') and (Value[1] = '"') then
716     begin
717     Delete(Value, 1, 1);
718     Delete(Value, Length(Value), 1);
719     Value := StringReplace (Value, '""', '"', [rfReplaceAll]);
720     end
721     else
722     Value := AnsiUpperCase(Value);
723     end;
724     Result := Value;
725     end;
726    
727 tony 263 {Returns true if "w" is a Firebird SQL reserved word, and the
728     corresponding TSQLTokens value.}
729    
730     function FindReservedWord(w: AnsiString; var token: TSQLTokens): boolean;
731     var i: TSQLTokens;
732     begin
733     Result := true;
734     w := AnsiUpperCase(Trim(w));
735     for i := Low(TSQLReservedWords) to High(TSQLReservedWords) do
736     begin
737     if w = sqlReservedWords[i] then
738     begin
739     token := i;
740     Exit;
741     end;
742     if w < sqlReservedWords[i] then
743     break;
744     end;
745     Result := false;
746     end;
747    
748 tony 117 {Returns true if "w" is a Firebird SQL reserved word}
749 tony 45
750 tony 56 function IsReservedWord(w: AnsiString): boolean;
751 tony 263 var token: TSQLTokens;
752 tony 45 begin
753 tony 263 Result := FindReservedWord(w,token);
754 tony 45 end;
755    
756 tony 117 {Format an SQL Identifier according to SQL Dialect}
757    
758 tony 56 function QuoteIdentifier(Dialect: Integer; Value: AnsiString): AnsiString;
759 tony 45 begin
760 tony 311 Value := TrimRight(Value);
761 tony 45 if Dialect = 1 then
762 tony 311 Value := AnsiUpperCase(Value)
763 tony 45 else
764 tony 117 Value := '"' + StringReplace (Value, '""', '"', [rfReplaceAll]) + '"';
765 tony 45 Result := Value;
766     end;
767    
768 tony 107 const
769     ValidSQLIdentifierChars = ['A'..'Z','a'..'z','0'..'9','_','$'];
770    
771 tony 117 {Returns true if the value is a valid SQL Identifier - note lower case accepted}
772    
773 tony 107 function IsSQLIdentifier(Value: AnsiString): boolean;
774     var i: integer;
775     begin
776     Result := false;
777     for i := 1 to Length(Value) do
778     if not (Value[i] in ValidSQLIdentifierChars) then Exit;
779     Result := true;
780     end;
781    
782 tony 315 function SchemeToProtocol(scheme: AnsiString): TProtocolAll;
783     begin
784     scheme := AnsiUpperCase(scheme);
785     if scheme = 'INET' then
786     Result := inet
787     else
788     if scheme = 'INET4' then
789     Result := inet4
790     else
791     if scheme = 'INET6' then
792     Result := inet6
793     else
794     if scheme = 'XNET' then
795     Result := xnet
796     else
797     if scheme = 'WNET' then
798     Result := wnet
799     end;
800    
801 tony 117 {Extracts the Database Connect string from a Create Database Statement}
802    
803 tony 315 {$IF declared(TRegexpr)}
804 tony 117 function ExtractConnectString(const CreateSQL: AnsiString;
805     var ConnectString: AnsiString): boolean;
806     var RegexObj: TRegExpr;
807     begin
808     RegexObj := TRegExpr.Create;
809     try
810     {extact database file spec}
811     RegexObj.ModifierG := false; {turn off greedy matches}
812     RegexObj.ModifierI := true; {case insensitive match}
813     RegexObj.Expression := '^ *CREATE +(DATABASE|SCHEMA) +''(.*)''';
814     Result := RegexObj.Exec(CreateSQL);
815     if Result then
816 tony 143 ConnectString := RegexObj.Match[2];
817 tony 117 finally
818     RegexObj.Free;
819     end;
820     end;
821 tony 143
822     function ParseConnectString(ConnectString: AnsiString; var ServerName,
823     DatabaseName: AnsiString; var Protocol: TProtocolAll; var PortNo: AnsiString
824     ): boolean;
825 tony 231
826 tony 143 var RegexObj: TRegExpr;
827     begin
828     ServerName := '';
829     DatabaseName := ConnectString;
830     PortNo := '';
831     Protocol := unknownProtocol;
832     RegexObj := TRegExpr.Create;
833     try
834     {extact database file spec}
835     RegexObj.ModifierG := false; {turn off greedy matches}
836 tony 231 RegexObj.Expression := '^([a-zA-Z46]+)://([a-zA-Z0-9\-\.]*)(|:[0-9a-zA-Z\-]+)/(.*)$';
837 tony 143 Result := RegexObj.Exec(ConnectString);
838     if Result then
839     begin
840     {URL type connect string}
841 tony 315 Protocol := SchemeToProtocol(RegexObj.Match[1]);
842 tony 143 ServerName := RegexObj.Match[2];
843     if RegexObj.MatchLen[3] > 0 then
844     PortNo := system.Copy(ConnectString,RegexObj.MatchPos[3]+1,RegexObj.MatchLen[3]-1);
845     DatabaseName := RegexObj.Match[4];
846 tony 231 if ServerName = '' then
847     DatabaseName := '/' + DatabaseName;
848 tony 143 end
849     else
850     begin
851 tony 231 {URL type connect string - local loop}
852     RegexObj.Expression := '^([a-zA-Z46]+)://(.*)$';
853 tony 143 Result := RegexObj.Exec(ConnectString);
854     if Result then
855 tony 231 begin
856 tony 315 Protocol := SchemeToProtocol(RegexObj.Match[1]);
857 tony 231 DatabaseName := RegexObj.Match[2];
858     end
859 tony 143 else
860     begin
861 tony 231 RegexObj.Expression := '^([a-zA-Z]:\\.*)';
862 tony 143 Result := RegexObj.Exec(ConnectString);
863     if Result then
864 tony 231 Protocol := Local {Windows with leading drive ID}
865 tony 143 else
866     begin
867 tony 231 RegexObj.Expression := '^([a-zA-Z0-9\-\.]+)(|/[0-9a-zA-Z\-]+):(.*)$';
868 tony 143 Result := RegexObj.Exec(ConnectString);
869     if Result then
870     begin
871 tony 231 {Legacy TCP Format}
872 tony 143 ServerName := RegexObj.Match[1];
873     if RegexObj.MatchLen[2] > 0 then
874     PortNo := system.Copy(ConnectString,RegexObj.MatchPos[2]+1,RegexObj.MatchLen[2]-1);
875     DatabaseName := RegexObj.Match[3];
876 tony 231 Protocol := TCP;
877 tony 143 end
878     else
879     begin
880 tony 231 RegexObj.Expression := '^\\\\([a-zA-Z0-9\-\.]+)(|@[0-9a-zA-Z\-]+)\\(.*)$';
881     Result := RegexObj.Exec(ConnectString);
882     if Result then
883     begin
884     {Netbui}
885     ServerName := RegexObj.Match[1];
886     if RegexObj.MatchLen[2] > 0 then
887     PortNo := system.Copy(ConnectString,RegexObj.MatchPos[2]+1,RegexObj.MatchLen[2]-1);
888     DatabaseName := RegexObj.Match[3];
889     Protocol := NamedPipe
890     end
891     else
892     begin
893     Result := true;
894     Protocol := Local; {Assume local}
895     end;
896 tony 143 end;
897     end;
898     end;
899     end;
900     finally
901     RegexObj.Free;
902     end;
903     end;
904    
905 tony 315 {$ELSE}
906     {$IF declared(TRegex)}
907     function ExtractConnectString(const CreateSQL: AnsiString;
908     var ConnectString: AnsiString): boolean;
909     var Regex: TRegEx;
910     Match: TMatch;
911 tony 143 begin
912 tony 315 Regex := TRegEx.Create('^ *CREATE +(DATABASE|SCHEMA) +''(.*)''',[roIgnoreCase]);
913     {extact database file spec}
914     Match := Regex.Match(CreateSQL);
915     Result := Match.Success and (Match.Groups.Count = 3);
916     if Result then
917     ConnectString := Match.Groups[2].Value;
918 tony 143 end;
919    
920 tony 315 function ParseConnectString(ConnectString: AnsiString; var ServerName,
921     DatabaseName: AnsiString; var Protocol: TProtocolAll; var PortNo: AnsiString
922     ): boolean;
923    
924     var Regex: TRegEx;
925     Match: TMatch;
926     begin
927     ServerName := '';
928     DatabaseName := ConnectString;
929     PortNo := '';
930     Protocol := unknownProtocol;
931     {extact database file spec}
932     Match := Regex.Match(ConnectString,'^([a-zA-Z46]+)://([a-zA-Z0-9\-\.]*)(|:[0-9a-zA-Z\-]+)/(.*)$',[roIgnoreCase]);
933     Result := Match.Success and (Match.Groups.Count = 5);
934     if Result then
935     begin
936     {URL type connect string}
937     Protocol := SchemeToProtocol(Match.Groups[1].Value);
938     ServerName := Match.Groups[2].Value;
939     PortNo := Match.Groups[3].Value;
940     DatabaseName := Match.Groups[4].Value;
941     if ServerName = '' then
942     DatabaseName := '/' + DatabaseName;
943     end
944     else
945     begin
946     {URL type connect string - local loop}
947     Match := Regex.Match(ConnectString,'^([a-zA-Z46]+)://(.*)$',[roIgnoreCase]);
948     Result := Match.Success and (Match.Groups.Count = 3);
949     if Result then
950     begin
951     Protocol := SchemeToProtocol(Match.Groups[1].Value);
952     DatabaseName := Match.Groups[2].Value;
953     end
954     else
955     begin
956     Match := Regex.Match(ConnectString,'^([a-zA-Z]:\\.*)',[roIgnoreCase]);
957     Result := Match.Success;
958     if Result then
959     Protocol := Local {Windows with leading drive ID}
960     else
961     begin
962     Match := Regex.Match(ConnectString,'^([a-zA-Z0-9\-\.]+)(|/[0-9a-zA-Z\-]+):(.*)$',[roIgnoreCase]);
963     Result := Match.Success and (Match.Groups.Count = 4);
964     if Result then
965     begin
966     {Legacy TCP Format}
967     ServerName := Match.Groups[1].Value;
968     PortNo := Match.Groups[2].Value;
969     DatabaseName := Match.Groups[3].Value;
970     Protocol := TCP;
971     end
972     else
973     begin
974     Match := Regex.Match(ConnectString,'^\\\\([a-zA-Z0-9\-\.]+)(|@[0-9a-zA-Z\-]+)\\(.*)$',[roIgnoreCase]);
975     Result := Match.Success and (Match.Groups.Count = 4);
976     if Result then
977     begin
978     {Netbui}
979     ServerName := Match.Groups[1].Value;
980     PortNo := Match.Groups[2].Value;
981     DatabaseName := Match.Groups[3].Value;
982     Protocol := NamedPipe
983     end
984     else
985     begin
986     Result := true;
987     Protocol := Local; {Assume local}
988     end;
989     end;
990     end;
991     end;
992     end;
993     end;
994 tony 118 {$ELSE}
995 tony 315 {cruder version of above for Delphi < XE. Older versions lack regular expression
996 tony 121 handling.}
997 tony 118 function ExtractConnectString(const CreateSQL: AnsiString;
998     var ConnectString: AnsiString): boolean;
999     var i: integer;
1000     begin
1001     Result := false;
1002     i := Pos('''',CreateSQL);
1003     if i > 0 then
1004     begin
1005     ConnectString := CreateSQL;
1006     delete(ConnectString,1,i);
1007     i := Pos('''',ConnectString);
1008     if i > 0 then
1009     begin
1010     delete(ConnectString,i,Length(ConnectString)-i+1);
1011     Result := true;
1012     end;
1013     end;
1014     end;
1015 tony 143
1016     function ParseConnectString(ConnectString: AnsiString;
1017     var ServerName, DatabaseName: AnsiString; var Protocol: TProtocolAll;
1018     var PortNo: AnsiString): boolean;
1019     begin
1020     Result := false;
1021     end;
1022    
1023 tony 315 {$IFEND}
1024     {$IFEND}
1025 tony 117
1026 tony 315 function GetProtocol(ConnectString: AnsiString): TProtocolAll;
1027     var ServerName,
1028     DatabaseName: AnsiString;
1029     PortNo: AnsiString;
1030     begin
1031     if not ParseConnectString(ConnectString,ServerName,DatabaseName,Result,PortNo) then
1032     Result := unknownProtocol;
1033     end;
1034    
1035 tony 143 {Make a connect string in format appropriate protocol}
1036    
1037     function MakeConnectString(ServerName, DatabaseName: AnsiString;
1038     Protocol: TProtocol; PortNo: AnsiString): AnsiString;
1039 tony 231
1040     function FormatURL: AnsiString;
1041     begin
1042     if (ServerName = '') and (Pos('/',DatabaseName) <= 1) then
1043     Result := DatabaseName
1044     else
1045     Result := ServerName + '/' + DatabaseName;
1046     end;
1047    
1048 tony 143 begin
1049 tony 315 if ServerName = '' then ServerName := 'localhost';
1050 tony 143 if PortNo <> '' then
1051     case Protocol of
1052     NamedPipe:
1053     ServerName := ServerName + '@' + PortNo;
1054     Local,
1055     SPX,
1056     xnet: {do nothing};
1057     TCP:
1058     ServerName := ServerName + '/' + PortNo;
1059     else
1060     ServerName := ServerName + ':' + PortNo;
1061     end;
1062    
1063     case Protocol of
1064     TCP: Result := ServerName + ':' + DatabaseName; {do not localize}
1065     SPX: Result := ServerName + '@' + DatabaseName; {do not localize}
1066     NamedPipe: Result := '\\' + ServerName + '\' + DatabaseName; {do not localize}
1067     Local: Result := DatabaseName; {do not localize}
1068 tony 231 inet: Result := 'inet://' + FormatURL; {do not localize}
1069     inet4: Result := 'inet4://' + FormatURL; {do not localize}
1070     inet6: Result := 'inet6://' + FormatURL; {do not localize}
1071     wnet: Result := 'wnet://' + FormatURL; {do not localize}
1072     xnet: Result := 'xnet://' + FormatURL; {do not localize}
1073 tony 143 end;
1074     end;
1075    
1076 tony 117 {Format an SQL Identifier according to SQL Dialect with encapsulation if necessary}
1077    
1078 tony 56 function QuoteIdentifierIfNeeded(Dialect: Integer; Value: AnsiString): AnsiString;
1079 tony 45 begin
1080 tony 311 Value := TrimRight(Value);
1081 tony 45 if (Dialect = 3) and
1082 tony 115 (IsReservedWord(Value) or not IsSQLIdentifier(Value) or (AnsiUpperCase(Value) <> Value)) then
1083 tony 117 Result := '"' + StringReplace (TrimRight(Value), '"', '""', [rfReplaceAll]) + '"'
1084 tony 45 else
1085     Result := Value
1086     end;
1087    
1088 tony 117 {Replaces unknown characters in a string with underscores}
1089    
1090 tony 56 function Space2Underscore(s: AnsiString): AnsiString;
1091 tony 45 var
1092     k: integer;
1093     begin
1094     Result := s;
1095     for k := 1 to Length(s) do
1096 tony 117 if not (Result[k] in ValidSQLIdentifierChars) then
1097 tony 45 Result[k] := '_';
1098     end;
1099    
1100 tony 117 {Reformats an SQL string with single quotes duplicated.}
1101    
1102 tony 56 function SQLSafeString(const s: AnsiString): AnsiString;
1103 tony 47 begin
1104     Result := StringReplace(s,'''','''''',[rfReplaceAll]);
1105     end;
1106 tony 45
1107 tony 270 { TSQLParamProcessor }
1108    
1109     function TSQLParamProcessor.DoExecute(GenerateParamNames: boolean;
1110     var slNames: TStrings): AnsiString;
1111     var token: TSQLTokens;
1112     iParamSuffix: Integer;
1113     begin
1114     Result := '';
1115     iParamSuffix := 0;
1116    
1117     while not EOF do
1118     begin
1119     token := GetNextToken;
1120     case token of
1121     sqltParam,
1122     sqltQuotedParam:
1123     begin
1124     Result := Result + '?';
1125     slNames.Add(TokenText);
1126     end;
1127    
1128     sqltPlaceHolder:
1129     if GenerateParamNames then
1130     begin
1131     Inc(iParamSuffix);
1132     slNames.AddObject(sIBXParam + IntToStr(iParamSuffix),self); //Note local convention
1133     //add pointer to self to mark entry
1134     Result := Result + '?';
1135     end
1136     else
1137     IBError(ibxeSQLParseError, [SParamNameExpected]);
1138    
1139     sqltQuotedString:
1140     Result := Result + '''' + SQLSafeString(TokenText) + '''';
1141    
1142     sqltIdentifierInDoubleQuotes:
1143     Result := Result + '"' + StringReplace(TokenText,'"','""',[rfReplaceAll]) + '"';
1144    
1145     sqltComment:
1146     Result := Result + '/*' + TokenText + '*/';
1147    
1148     sqltCommentLine:
1149 tony 287 Result := Result + '--' + TokenText + LineEnding;
1150 tony 270
1151     sqltEOL:
1152     Result := Result + LineEnding;
1153    
1154     else
1155     Result := Result + TokenText;
1156     end;
1157     end;
1158     end;
1159    
1160     function TSQLParamProcessor.GetChar: AnsiChar;
1161     begin
1162     if FIndex <= Length(FInString) then
1163     begin
1164     Result := FInString[FIndex];
1165     Inc(FIndex);
1166     end
1167     else
1168     Result := #0;
1169     end;
1170    
1171     class function TSQLParamProcessor.Execute(sSQL: AnsiString;
1172     GenerateParamNames: boolean; var slNames: TStrings): AnsiString;
1173     begin
1174     with self.Create do
1175     try
1176     FInString := sSQL;
1177     FIndex := 1;
1178     Result := DoExecute(GenerateParamNames,slNames);
1179     finally
1180     Free;
1181     end;
1182     end;
1183    
1184 tony 263 { TSQLwithNamedParamsTokeniser }
1185    
1186     procedure TSQLwithNamedParamsTokeniser.Assign(source: TSQLTokeniser);
1187     begin
1188     inherited Assign(source);
1189     if source is TSQLwithNamedParamsTokeniser then
1190     begin
1191     FState := TSQLwithNamedParamsTokeniser(source).FState;
1192     FNested := TSQLwithNamedParamsTokeniser(source).FNested;
1193     end;
1194     end;
1195    
1196     procedure TSQLwithNamedParamsTokeniser.Reset;
1197     begin
1198     inherited Reset;
1199     FState := stInit;
1200     FNested := 0;
1201     end;
1202    
1203     function TSQLwithNamedParamsTokeniser.TokenFound(var token: TSQLTokens
1204     ): boolean;
1205     begin
1206     Result := inherited TokenFound(token);
1207     if not Result then Exit;
1208    
1209     case FState of
1210     stInit:
1211     begin
1212     case token of
1213     sqltColon:
1214     begin
1215     FState := stInParam;
1216     ResetQueue(token);
1217     end;
1218    
1219     sqltBegin:
1220     begin
1221     FState := stInBlock;
1222     FNested := 1;
1223     end;
1224    
1225     sqltOpenSquareBracket:
1226     FState := stInArrayDim;
1227    
1228     end;
1229     end;
1230    
1231     stInParam:
1232     begin
1233     case token of
1234     sqltIdentifier:
1235     token := sqltParam;
1236    
1237     sqltIdentifierInDoubleQuotes:
1238     token := sqltQuotedParam;
1239    
1240     else
1241     begin
1242     QueueToken(token);
1243     ReleaseQueue(token);
1244     end;
1245     end;
1246     FState := stInit;
1247     end;
1248    
1249     stInBlock:
1250     begin
1251     case token of
1252 tony 348 sqltBegin,
1253     sqltCase:
1254 tony 263 Inc(FNested);
1255    
1256     sqltEnd:
1257     begin
1258     Dec(FNested);
1259     if FNested = 0 then
1260     FState := stInit;
1261     end;
1262     end;
1263     end;
1264    
1265     stInArrayDim:
1266     begin
1267     if token = sqltCloseSquareBracket then
1268     FState := stInit;
1269     end;
1270     end;
1271    
1272     Result := (FState <> stInParam);
1273     end;
1274    
1275     { TSQLTokeniser }
1276    
1277     function TSQLTokeniser.GetNext: TSQLTokens;
1278     var C: AnsiChar;
1279     begin
1280     if EOF then
1281     Result := sqltEOF
1282     else
1283     begin
1284     C := GetChar;
1285     case C of
1286     #0:
1287     Result := sqltEOF;
1288     ' ',TAB:
1289     Result := sqltSpace;
1290     '0'..'9':
1291     Result := sqltNumberString;
1292     ';':
1293     Result := sqltSemiColon;
1294     '?':
1295     Result := sqltPlaceholder;
1296     '|':
1297     Result := sqltPipe;
1298     '"':
1299     Result := sqltDoubleQuotes;
1300     '''':
1301     Result := sqltSingleQuotes;
1302     '/':
1303     Result := sqltForwardSlash;
1304 tony 270 '\':
1305     Result := sqltBackslash;
1306 tony 263 '*':
1307     Result := sqltAsterisk;
1308     '(':
1309     Result := sqltOpenBracket;
1310     ')':
1311     Result := sqltCloseBracket;
1312     ':':
1313     Result := sqltColon;
1314     ',':
1315     Result := sqltComma;
1316     '.':
1317     Result := sqltPeriod;
1318     '=':
1319     Result := sqltEquals;
1320     '[':
1321     Result := sqltOpenSquareBracket;
1322     ']':
1323     Result := sqltCloseSquareBracket;
1324 tony 287 '-':
1325     Result := sqltMinus;
1326 tony 263 '<':
1327     Result := sqltLT;
1328     '>':
1329     Result := sqltGT;
1330     CR:
1331     Result := sqltCR;
1332     LF:
1333     Result := sqltEOL;
1334     else
1335     if C in ValidSQLIdentifierChars then
1336     Result := sqltIdentifier
1337     else
1338     Result := sqltOtherCharacter;
1339     end;
1340     FLastChar := C
1341     end;
1342     FNextToken := Result;
1343     end;
1344    
1345     procedure TSQLTokeniser.PopQueue(var token: TSQLTokens);
1346     begin
1347     if FQFirst = FQLast then
1348     IBError(ibxeTokenQueueUnderflow,[]);
1349     token := FTokenQueue[FQFirst].token;
1350     FString := FTokenQueue[FQFirst].text;
1351     Inc(FQFirst);
1352     if FQFirst = FQLast then
1353     FQueueState := tsHold;
1354     end;
1355    
1356     procedure TSQLTokeniser.Assign(source: TSQLTokeniser);
1357     begin
1358     FString := source.FString;
1359     FNextToken := source.FNextToken;
1360     FTokenQueue := source.FTokenQueue;
1361     FQueueState := source.FQueueState;
1362     FQFirst := source.FQFirst;
1363     FQLast := source.FQLast;
1364     end;
1365    
1366     function TSQLTokeniser.TokenFound(var token: TSQLTokens): boolean;
1367     begin
1368     Result := (FState = stDefault);
1369     if Result and (token = sqltIdentifier) then
1370     FindReservedWord(FString,token);
1371     end;
1372    
1373     procedure TSQLTokeniser.QueueToken(token: TSQLTokens; text: AnsiString);
1374     begin
1375     if FQLast > TokenQueueMaxSize then
1376     IBError(ibxeTokenQueueOverflow,[]);
1377     FTokenQueue[FQLast].token := token;
1378     FTokenQueue[FQLast].text := text;
1379     Inc(FQLast);
1380     end;
1381    
1382     procedure TSQLTokeniser.QueueToken(token: TSQLTokens);
1383     begin
1384     QueueToken(token,TokenText);
1385     end;
1386    
1387     procedure TSQLTokeniser.ResetQueue;
1388     begin
1389     FQFirst := 0;
1390     FQLast := 0;
1391     FQueueState := tsHold;
1392     end;
1393    
1394     procedure TSQLTokeniser.ResetQueue(token: TSQLTokens; text: AnsiString);
1395     begin
1396     ResetQueue;
1397     QueueToken(token,text);
1398     end;
1399    
1400     procedure TSQLTokeniser.ResetQueue(token: TSQLTokens);
1401     begin
1402     ResetQueue;
1403     QueueToken(token);
1404     end;
1405    
1406     procedure TSQLTokeniser.ReleaseQueue(var token: TSQLTokens);
1407     begin
1408     FQueueState := tsRelease;
1409     PopQueue(token);
1410     end;
1411    
1412     procedure TSQLTokeniser.ReleaseQueue;
1413     begin
1414     FQueueState := tsRelease;
1415     end;
1416    
1417     function TSQLTokeniser.GetQueuedText: AnsiString;
1418     var i: integer;
1419     begin
1420     Result := '';
1421     for i := FQFirst to FQLast do
1422     Result := Result + FTokenQueue[i].text;
1423     end;
1424    
1425     procedure TSQLTokeniser.SetTokenText(text: AnsiString);
1426     begin
1427     FString := text;
1428     end;
1429    
1430     constructor TSQLTokeniser.Create;
1431     begin
1432     inherited Create;
1433     Reset;
1434     end;
1435    
1436     destructor TSQLTokeniser.Destroy;
1437     begin
1438     Reset;
1439     inherited Destroy;
1440     end;
1441    
1442     procedure TSQLTokeniser.Reset;
1443     begin
1444     FNextToken := sqltInit;
1445     FState := stDefault;
1446     FString := '';
1447     FEOF := false;
1448     ResetQueue;
1449     end;
1450    
1451     function TSQLTokeniser.GetNextToken: TSQLTokens;
1452     begin
1453     if FQueueState = tsRelease then
1454     repeat
1455     PopQueue(Result);
1456     FEOF := Result = sqltEOF;
1457     if TokenFound(Result) then
1458     Exit;
1459     until FQueueState <> tsRelease;
1460    
1461     Result := InternalGetNextToken;
1462     end;
1463    
1464     {a simple lookahead one algorithm to extra the next symbol}
1465    
1466     function TSQLTokeniser.InternalGetNextToken: TSQLTokens;
1467     var C: AnsiChar;
1468     begin
1469     Result := sqltEOF;
1470    
1471     if FNextToken = sqltInit then
1472     GetNext;
1473    
1474     repeat
1475     Result := FNextToken;
1476     C := FLastChar;
1477     GetNext;
1478    
1479     if FSkipNext then
1480     begin
1481     FSkipNext := false;
1482     continue;
1483     end;
1484    
1485     case FState of
1486     stInComment:
1487     begin
1488     if (Result = sqltAsterisk) and (FNextToken = sqltForwardSlash) then
1489     begin
1490     FState := stDefault;
1491     Result := sqltComment;
1492     GetNext;
1493     end
1494     else
1495     FString := FString + C;
1496     end;
1497    
1498     stInCommentLine:
1499     begin
1500     case Result of
1501     sqltEOL:
1502     begin
1503     FState := stDefault;
1504     Result := sqltCommentLine;
1505     end;
1506    
1507     sqltCR: {ignore};
1508    
1509     else
1510     FString := FString + C;
1511     end;
1512     end;
1513    
1514     stSingleQuoted:
1515     begin
1516     if (Result = sqltSingleQuotes) then
1517     begin
1518     if (FNextToken = sqltSingleQuotes) then
1519     begin
1520     FSkipNext := true;
1521     FString := FString + C;
1522     end
1523     else
1524     begin
1525     Result := sqltQuotedString;
1526     FState := stDefault;
1527     end;
1528     end
1529     else
1530     FString := FString + C;
1531     end;
1532    
1533     stDoubleQuoted:
1534     begin
1535     if (Result = sqltDoubleQuotes) then
1536     begin
1537     if (FNextToken = sqltDoubleQuotes) then
1538     begin
1539     FSkipNext := true;
1540     FString := FString + C;
1541     end
1542     else
1543     begin
1544     Result := sqltIdentifierInDoubleQuotes;
1545     FState := stDefault;
1546     end;
1547     end
1548     else
1549     FString := FString + C;
1550     end;
1551    
1552     stInIdentifier:
1553     begin
1554     FString := FString + C;
1555     Result := sqltIdentifier;
1556     if not (FNextToken in [sqltIdentifier,sqltNumberString]) then
1557     FState := stDefault
1558     end;
1559    
1560     stInNumeric:
1561     begin
1562     FString := FString + C;
1563     if (Result = sqltPeriod) and (FNextToken = sqltPeriod) then
1564     begin
1565     {malformed decimal}
1566     FState := stInIdentifier;
1567     Result := sqltIdentifier
1568     end
1569     else
1570     begin
1571     if not (FNextToken in [sqltNumberString,sqltPeriod]) then
1572     FState := stDefault;
1573     Result := sqltNumberString;
1574     end;
1575     end;
1576    
1577     else {stDefault}
1578     begin
1579     FString := C;
1580     case Result of
1581    
1582     sqltPipe:
1583     if FNextToken = sqltPipe then
1584     begin
1585     Result := sqltConcatSymbol;
1586     FString := C + FLastChar;
1587     GetNext;
1588     end;
1589    
1590     sqltForwardSlash:
1591     begin
1592     if FNextToken = sqltAsterisk then
1593     begin
1594     FString := '';
1595     GetNext;
1596     FState := stInComment;
1597     end
1598 tony 287 end;
1599    
1600     sqltMinus:
1601     begin
1602     if FNextToken = sqltMinus then
1603 tony 263 begin
1604     FString := '';
1605     GetNext;
1606     FState := stInCommentLine;
1607     end;
1608     end;
1609    
1610     sqltSingleQuotes:
1611     begin
1612     FString := '';
1613     FState := stSingleQuoted;
1614     end;
1615    
1616     sqltDoubleQuotes:
1617     begin
1618     FString := '';
1619     FState := stDoubleQuoted;
1620     end;
1621    
1622     sqltIdentifier:
1623 tony 265 if FNextToken in [sqltIdentifier,sqltNumberString] then
1624 tony 263 FState := stInIdentifier;
1625    
1626     sqltNumberString:
1627     if FNextToken in [sqltNumberString,sqltPeriod] then
1628     FState := stInNumeric;
1629     end;
1630     end;
1631     end;
1632    
1633     // writeln(FString);
1634     FEOF := Result = sqltEOF;
1635     until TokenFound(Result) or EOF;
1636     end;
1637    
1638 tony 315 function ParseDateTimeTZString(aDateTimeStr: Ansistring; var aDateTime: TDateTime;
1639     var aTimezone: AnsiString; TimeOnly: boolean): boolean;
1640     {$IF declared(TFormatSettings)}
1641     begin
1642     {$IF declared(DefaultFormatSettings)}
1643     Result := ParseDateTimeTZString(aDateTimeStr,aDateTime,aTimeZone,DefaultFormatSettings,TimeOnly);
1644     {$ELSE}
1645     {$IF declared(FormatSettings)}
1646     Result := ParseDateTimeTZString(aDateTimeStr,aDateTime,aTimeZone,FormatSettings,TimeOnly);
1647     {$IFEND} {$IFEND}
1648     end;
1649    
1650     function ParseDateTimeTZString(aDateTimeStr: Ansistring; var aDateTime: TDateTime;
1651     var aTimezone: AnsiString; aFormatSettings: TFormatSettings; TimeOnly: boolean=false): boolean;
1652     {$IFEND}
1653     const
1654     whitespacechars = [' ',#$09,#$0A,#$0D];
1655     var i,j,l: integer;
1656     aTime: TDateTime;
1657     DMs: longint;
1658     begin
1659     Result := false;
1660     aTimezone := '';
1661     if aDateTimeStr <> '' then
1662     {$if declared(TFormatSettings)}
1663     with aFormatSettings do
1664     {$IFEND}
1665     begin
1666     aDateTime := 0;
1667     {Parse to get time zone info}
1668     i := 1;
1669     while (i <= length(aDateTimeStr)) and (aDateTimeStr[i] in whitespacechars) do inc(i); {skip white space}
1670     if not TimeOnly then
1671     begin
1672     {decode date}
1673     j := i;
1674     while (j <= length(aDateTimeStr)) and (aDateTimeStr[j] in ['0'..'9',DateSeparator]) do inc(j);
1675     if TryStrToDate(system.copy(aDateTimeStr,i,j-i),aDateTime) then
1676     i := j; {otherwise start again i.e. assume time only}
1677     end;
1678    
1679     while (i <= length(aDateTimeStr)) and (aDateTimeStr[i] in whitespacechars) do inc(i); {skip white space}
1680     {decode time}
1681     j := i;
1682     while (j <= length(aDateTimeStr)) and (aDateTimeStr[j] in ['0'..'9',TimeSeparator]) do inc(j);
1683     Result := TryStrToTime(system.copy(aDateTimeStr,i,j-i),aTime);
1684     if not Result then Exit;
1685     aDateTime := aDateTime + aTime;
1686     i := j;
1687    
1688     {is there a factional second part}
1689     if (i <= length(aDateTimeStr)) and (aDateTimeStr[i] = '.') then
1690     begin
1691     inc(i);
1692     inc(j);
1693     while (j <= Length(aDateTimeStr)) and (aDateTimeStr[j] in ['0'..'9']) do inc(j);
1694     if j > i then
1695     begin
1696     l := j-i;
1697     if l > 4 then l := 4;
1698     Result := TryStrToInt(system.copy(aDateTimeStr,i,l),DMs);
1699     if not Result then Exit;
1700    
1701     {adjust for number of significant digits}
1702     case l of
1703     3: DMs := DMs * 10;
1704     2: DMs := DMs * 100;
1705     1: DMs := DMs * 1000;
1706     end;
1707     aDateTime := aDateTime + (DMs / (MsecsPerDay*10));
1708     end;
1709     end;
1710     i := j;
1711    
1712     while (i <= length(aDateTimeStr)) and (aDateTimeStr[i] in whitespacechars) do inc(i); {skip white space}
1713     {decode time zone}
1714     if i < length(aDateTimeStr) then
1715     begin
1716     j := i;
1717     while (j <= length(aDateTimeStr)) and not (aDateTimeStr[j] in whitespacechars) do inc(j);
1718     aTimezone := system.copy(aDateTimeStr,i,j-i);
1719     end;
1720     Result := true;
1721     end
1722     end;
1723    
1724     {The following is similar to FPC DecodeTime except that the Firebird standard
1725     decimilliseconds is used instead of milliseconds for fractional seconds}
1726    
1727     procedure FBDecodeTime(aTime: TDateTime; var Hour, Minute, Second: word;
1728     var DeciMillisecond: cardinal);
1729     var D : Double;
1730     l : cardinal;
1731     begin
1732     {conversion to decimilliseconds hacked from FPC DateTimeToTimeStamp}
1733     D := aTime * MSecsPerDay *10;
1734     if D < 0 then
1735     D := D - 0.5
1736     else
1737     D := D + 0.5;
1738     {rest hacked from FPC DecodeTIme}
1739     l := Abs(Trunc(D)) Mod (MSecsPerDay*10);
1740     Hour := l div 36000000;
1741     l := l mod 36000000;
1742     Minute := l div 600000;
1743     l := l mod 600000;
1744     Second := l div 10000;
1745     DeciMillisecond := l mod 10000;
1746     end;
1747    
1748     {The following is similar to FPC EncodeTime except that the Firebird standard
1749     decimilliseconds is used instead of milliseconds for fractional seconds}
1750    
1751     function FBEncodeTime(Hour, Minute, Second, DeciMillisecond: cardinal): TDateTime;
1752     const DMSecsPerDay = MSecsPerDay*10;
1753     var DMs: cardinal;
1754     D: Double;
1755     begin
1756     if (Hour<24) and (Minute<60) and (Second<60) and (DeciMillisecond<10000) then
1757     begin
1758     DMs := Hour*36000000+Minute*600000+Second*10000+DeciMillisecond;
1759     D := DMs/DMSecsPerDay;
1760     Result:=TDateTime(d)
1761     end
1762     else
1763     IBError(ibxeBadTimeSpecification,[Hour, Minute, Second, DeciMillisecond]);
1764     end;
1765    
1766     {The following is similar to FPC FormatDateTime except that it additionally
1767     allows the timstamp to have a fractional seconds component with a resolution
1768     of four decimal places. This is appended to the result for FormatDateTime
1769     if the format string contains a "zzzz' string.}
1770    
1771     function FBFormatDateTime(fmt: AnsiString; aDateTime: TDateTime): AnsiString;
1772     var Hour, Minute, Second: word;
1773     DeciMillisecond: cardinal;
1774     begin
1775     if Pos('zzzz',fmt) > 0 then
1776     begin
1777     FBDecodeTime(aDateTime, Hour, Minute, Second, DeciMillisecond);
1778     fmt := StringReplace(fmt, 'zzzz', Format('%.4d',[DeciMillisecond]), [rfReplaceAll]);
1779     end;
1780     Result := FormatDateTime(fmt,aDateTime);
1781     end;
1782    
1783     function FormatTimeZoneOffset(EffectiveTimeOffsetMins: integer): AnsiString;
1784     begin
1785     if EffectiveTimeOffsetMins > 0 then
1786     Result := Format('+%.2d:%.2d',[EffectiveTimeOffsetMins div 60,abs(EffectiveTimeOffsetMins mod 60)])
1787     else
1788     Result := Format('%.2d:%.2d',[EffectiveTimeOffsetMins div 60,abs(EffectiveTimeOffsetMins mod 60)]);
1789     end;
1790    
1791     function DecodeTimeZoneOffset(TZOffset: AnsiString; var dstOffset: integer): boolean;
1792     var i: integer;
1793     begin
1794     Result := false;
1795     TZOffset := Trim(TZOffset);
1796     for i := 1 to Length(TZOffset) do
1797     if not (TZOffset[i] in ['0'..'9','-','+',':']) then Exit;
1798    
1799     Result := true;
1800     i := Pos(':',TZOffset);
1801     if i > 0 then
1802     dstOffset := StrToInt(copy(TZOffset,1,i-1)) * 60 + StrToInt(copy(TZOffset,i + 1))
1803     else
1804     dstOffset := StrToInt(TZOffset) * 60;
1805     end;
1806    
1807     function StripLeadingZeros(Value: AnsiString): AnsiString;
1808     var i: Integer;
1809     start: integer;
1810     begin
1811     Result := '';
1812     start := 1;
1813     if (Length(Value) > 0) and (Value[1] = '-') then
1814     begin
1815     Result := '-';
1816     start := 2;
1817     end;
1818     for i := start to Length(Value) do
1819     if Value[i] <> '0' then
1820     begin
1821     Result := Result + system.copy(Value, i, MaxInt);
1822     Exit;
1823     end;
1824     end;
1825    
1826 tony 45 end.