ViewVC Help
View File | Revision Log | Show Annotations | Download File | View Changeset | Root Listing
root/public/ibx/trunk/fbintf/client/IBUtils.pas
Revision: 402
Committed: Mon Aug 1 10:07:24 2022 UTC (20 months, 4 weeks ago) by tony
Content type: text/x-pascal
File size: 74892 byte(s)
Log Message:
IBX Release 2.5.0

File Contents

# Content
1 {************************************************************************}
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 {$IFDEF MSWINDOWS}
36 {$DEFINE WINDOWS}
37 {$ENDIF}
38
39 {$IFDEF FPC}
40 {$Mode Delphi}
41 {$codepage UTF8}
42 {$ENDIF}
43
44 { $IF declared(CompilerVersion) and (CompilerVersion >= 22)}
45 { $define HASDELPHIREQEX}
46 { $IFEND}
47
48 interface
49
50 uses Classes, SysUtils, IB;
51
52 {$IF not defined(LineEnding)}
53 const
54 {$IFDEF WINDOWS}
55 LineEnding = #$0D#$0A;
56 {$ELSE}
57 LineEnding = #$0A;
58 {$ENDIF}
59 {$IFEND}
60
61 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 sqltBackslash,
273 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 sqltMinus,
295 sqltConcatSymbol,
296 sqltLT,
297 sqltGT,
298 sqltCR,
299 sqltEOL,
300 sqltEOF,
301 sqltInit
302 );
303
304 TSQLReservedWords = sqltAdd..sqltYear;
305
306 const
307 CRLF = #13 + #10;
308 CR = #13;
309 LF = #10;
310 TAB = #9;
311 NULL_TERMINATOR = #0;
312
313 {SQL Reserved words in alphabetical order}
314
315 sqlReservedWords: array [TSQLReservedWords] of string = (
316 '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 'KEY',
408 '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 'ROWS',
468 '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
516 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 function ReadCharacters(NumOfChars: integer): AnsiString;
567
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 { 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 TOnProgressEvent = procedure (Sender: TObject; Reset: boolean; value: integer) of object;
624
625 { 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: AnsiString;
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: AnsiString;
666 columnName: AnsiString;
667 dim: cardinal;
668 Size: cardinal;
669 Scale: integer;
670 CharSet: AnsiString;
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: AnsiString;
682 FBlobData: array of TBlobData;
683 FCurrentBlob: integer;
684 FArrayData: array of TArrayData;
685 FCurrentArray: integer;
686 FXMLString: AnsiString;
687 function FindTag(tag: AnsiString; var xmlTag: TXMLTag): boolean;
688 function GetArrayData(index: integer): TArrayData;
689 function GetArrayDataCount: integer;
690 function GetBlobData(index: integer): TBlobData;
691 function GetBlobDataCount: integer;
692 function GetTagName(xmltag: TXMLTag): AnsiString;
693 procedure ProcessAttributeValue(attrValue: AnsiString);
694 procedure ProcessBoundsList(boundsList: AnsiString);
695 procedure ProcessTagValue(tagValue: AnsiString);
696 procedure XMLTagInit(xmltag: TXMLTag);
697 function XMLTagEnd(var xmltag: TXMLTag): boolean;
698 procedure XMLTagEnter;
699 protected
700 function GetAttachment: IAttachment; virtual; abstract;
701 function GetTransaction: ITransaction; virtual; abstract;
702 function GetErrorPrefix: AnsiString; virtual; abstract;
703 function TokenFound(var token: TSQLTokens): boolean; override;
704 procedure Reset; override;
705 procedure ShowError(msg: AnsiString; params: array of const); overload; virtual;
706 procedure ShowError(msg: AnsiString); overload;
707 public
708 constructor Create;
709 destructor Destroy; override;
710 procedure FreeDataObjects;
711 class function FormatBlob(Field: ISQLData): AnsiString; overload;
712 class function FormatBlob(contents: AnsiString; subtype:integer): AnsiString; overload;
713 class function FormatArray(ar: IArray): AnsiString;
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, jeTransCommitFail, jeTransCommitRet, jeTransRollback,
724 jeTransRollbackFail, jeTransRollbackRet, jeQuery,jeUnknown);
725
726 PJnlEntry = ^TJnlEntry;
727 TJnlEntry = record
728 JnlEntryType: TJnlEntryType;
729 Timestamp: TDateTime;
730 AttachmentID: cardinal;
731 SessionID: cardinal;
732 TransactionID: cardinal;
733 OldTransactionID: cardinal;
734 TransactionName: AnsiString;
735 TPB: ITPB;
736 DefaultCompletion: TTransactionCompletion;
737 QueryText: AnsiString;
738 end;
739
740 TOnNextJournalEntry = procedure(JnlEntry: TJnlEntry) of object;
741
742 { TCustomJournalProcessor - used to parse a client side journal}
743
744 TCustomJournalProcessor = class(TSQLTokeniser)
745 private
746 type TLineState = (lsInit, lsJnlFound, lsGotTimestamp, lsGotJnlType,
747 lsGotAttachmentID, lsGotSessionID,
748 lsGotTransactionID, lsGotOldTransactionID, lsGotText1Length,
749 lsGotText1, lsGotText2Length, lsGotText2);
750 private
751 FOnNextJournalEntry: TOnNextJournalEntry;
752 FFirebirdClientAPI: IFirebirdAPI;
753 function IdentifyJnlEntry(aTokenText: AnsiString): TJnlEntryType;
754 protected
755 procedure DoExecute;
756 procedure DoNextJournalEntry(JnlEntry: TJnlEntry); virtual;
757 property OnNextJournalEntry: TOnNextJournalEntry read FOnNextJournalEntry write FOnNextJournalEntry;
758 public
759 constructor Create(api: IFirebirdAPI);
760 class function JnlEntryText(je: TJnlEntryType): string;
761 end;
762
763 { TJournalProcessor }
764
765 TJournalProcessor = class(TCustomJournalProcessor)
766 private
767 FInStream: TStream;
768 protected
769 function GetChar: AnsiChar; override;
770 public
771 destructor Destroy; override;
772 class procedure Execute( aFileName: string; api: IFirebirdAPI; aOnNextJournalEntry: TOnNextJournalEntry); overload;
773 class procedure Execute( S: TStream; api: IFirebirdAPI; aOnNextJournalEntry: TOnNextJournalEntry); overload;
774 end;
775
776
777 function Max(n1, n2: Integer): Integer;
778 function Min(n1, n2: Integer): Integer;
779 function RandomString(iLength: Integer): AnsiString;
780 function RandomInteger(iLow, iHigh: Integer): Integer;
781 function StripString(st: AnsiString; CharsToStrip: AnsiString): AnsiString;
782 function ExtractIdentifier(Dialect: Integer; Value: AnsiString): AnsiString;
783 function FindReservedWord(w: AnsiString; var token: TSQLTokens): boolean;
784 function IsReservedWord(w: AnsiString): boolean;
785 function QuoteIdentifier(Dialect: Integer; Value: AnsiString): AnsiString;
786 function QuoteIdentifierIfNeeded(Dialect: Integer; Value: AnsiString): AnsiString;
787 function Space2Underscore(s: AnsiString): AnsiString;
788 function SQLSafeString(const s: AnsiString): AnsiString;
789 function IsSQLIdentifier(Value: AnsiString): boolean;
790 function ExtractConnectString(const CreateSQL: AnsiString; var ConnectString: AnsiString): boolean;
791 function MakeConnectString(ServerName, DatabaseName: AnsiString; Protocol: TProtocol;
792 PortNo: AnsiString = ''): AnsiString;
793 function ParseConnectString(ConnectString: AnsiString;
794 var ServerName, DatabaseName: AnsiString; var Protocol: TProtocolAll;
795 var PortNo: AnsiString): boolean;
796 function GetProtocol(ConnectString: AnsiString): TProtocolAll;
797
798 {$IF declared(TFormatSettings)}
799 function ParseDateTimeTZString(aDateTimeStr: Ansistring; var aDateTime: TDateTime;
800 var aTimezone: AnsiString; aFormatSettings: TFormatSettings; TimeOnly: boolean=false): boolean; overload;
801 {$IFEND}
802 function ParseDateTimeTZString(aDateTimeStr: Ansistring; var aDateTime: TDateTime;
803 var aTimezone: AnsiString; TimeOnly: boolean=false): boolean; overload;
804 procedure FBDecodeTime(aTime: TDateTime; var Hour, Minute, Second: word; var DeciMillisecond: cardinal);
805 function FBEncodeTime(Hour, Minute, Second, DeciMillisecond: cardinal): TDateTime;
806 function FBFormatDateTime(fmt: AnsiString; aDateTime: TDateTime): AnsiString;
807 function FormatTimeZoneOffset(EffectiveTimeOffsetMins: integer): AnsiString;
808 function DecodeTimeZoneOffset(TZOffset: AnsiString; var dstOffset: integer): boolean;
809 function StripLeadingZeros(Value: AnsiString): AnsiString;
810 function StringToHex(octetString: string; MaxLineLength: integer=0): string; overload;
811 procedure StringToHex(octetString: string; TextOut: TStrings; MaxLineLength: integer=0); overload;
812
813
814 implementation
815
816 uses FBMessages, Math
817
818 {$IFDEF FPC}
819 ,RegExpr
820 {$ELSE}
821 {$IF declared(CompilerVersion) and (CompilerVersion >= 22)}
822 , RegularExpressions
823 {$IFEND}
824 {$ENDIF};
825
826 resourcestring
827 sXMLStackUnderflow = 'XML Stack Underflow';
828 sInvalidEndTag = 'XML End Tag Mismatch - %s';
829 sBadEndTagClosing = 'XML End Tag incorrectly closed';
830 sXMLStackOverFlow = 'XML Stack Overflow';
831 sXMLAttributeError = 'Unexpected attribute - "%s" = "%s"';
832 sInvalidBoundsList = 'Invalid array bounds list - "%s"';
833 sBinaryBlockMustbeEven = 'Binary block must have an even number of characters';
834 sArrayIndexError = 'Array Index Error (%d)';
835 sBlobIndexError = 'Blob Index Error (%d)';
836 sNoDatabase = 'Missing database for xml tag import';
837 sNoTransaction = 'Missing transaction for xml tag import';
838
839
840 function Max(n1, n2: Integer): Integer;
841 begin
842 if (n1 > n2) then
843 result := n1
844 else
845 result := n2;
846 end;
847
848 function Min(n1, n2: Integer): Integer;
849 begin
850 if (n1 < n2) then
851 result := n1
852 else
853 result := n2;
854 end;
855
856 function RandomString(iLength: Integer): AnsiString;
857 begin
858 result := '';
859 while Length(result) < iLength do
860 result := result + IntToStr(RandomInteger(0, High(Integer)));
861 if Length(result) > iLength then
862 result := Copy(result, 1, iLength);
863 end;
864
865 function RandomInteger(iLow, iHigh: Integer): Integer;
866 begin
867 result := Trunc(Random(iHigh - iLow)) + iLow;
868 end;
869
870 function StripString(st: AnsiString; CharsToStrip: AnsiString): AnsiString;
871 var
872 i: Integer;
873 begin
874 result := '';
875 for i := 1 to Length(st) do begin
876 if AnsiPos(st[i], CharsToStrip) = 0 then
877 result := result + st[i];
878 end;
879 end;
880
881 {Extracts SQL Identifier typically from a Dialect 3 encoding}
882
883 function ExtractIdentifier(Dialect: Integer; Value: AnsiString): AnsiString;
884 begin
885 Value := Trim(Value);
886 if Dialect = 1 then
887 Value := AnsiUpperCase(Value)
888 else
889 begin
890 if (Value <> '') and (Value[1] = '"') then
891 begin
892 Delete(Value, 1, 1);
893 Delete(Value, Length(Value), 1);
894 Value := StringReplace (Value, '""', '"', [rfReplaceAll]);
895 end
896 else
897 Value := AnsiUpperCase(Value);
898 end;
899 Result := Value;
900 end;
901
902 {Returns true if "w" is a Firebird SQL reserved word, and the
903 corresponding TSQLTokens value.}
904
905 function FindReservedWord(w: AnsiString; var token: TSQLTokens): boolean;
906 var i: TSQLTokens;
907 begin
908 Result := true;
909 w := AnsiUpperCase(Trim(w));
910 for i := Low(TSQLReservedWords) to High(TSQLReservedWords) do
911 begin
912 if w = sqlReservedWords[i] then
913 begin
914 token := i;
915 Exit;
916 end;
917 if w < sqlReservedWords[i] then
918 break;
919 end;
920 Result := false;
921 end;
922
923 {Returns true if "w" is a Firebird SQL reserved word}
924
925 function IsReservedWord(w: AnsiString): boolean;
926 var token: TSQLTokens;
927 begin
928 Result := FindReservedWord(w,token);
929 end;
930
931 {Format an SQL Identifier according to SQL Dialect}
932
933 function QuoteIdentifier(Dialect: Integer; Value: AnsiString): AnsiString;
934 begin
935 Value := TrimRight(Value);
936 if Dialect = 1 then
937 Value := AnsiUpperCase(Value)
938 else
939 Value := '"' + StringReplace (Value, '""', '"', [rfReplaceAll]) + '"';
940 Result := Value;
941 end;
942
943 const
944 ValidSQLIdentifierChars = ['A'..'Z','a'..'z','0'..'9','_','$'];
945
946 {Returns true if the value is a valid SQL Identifier - note lower case accepted}
947
948 function IsSQLIdentifier(Value: AnsiString): boolean;
949 var i: integer;
950 begin
951 Result := false;
952 for i := 1 to Length(Value) do
953 if not (Value[i] in ValidSQLIdentifierChars) then Exit;
954 Result := true;
955 end;
956
957 function SchemeToProtocol(scheme: AnsiString): TProtocolAll;
958 begin
959 scheme := AnsiUpperCase(scheme);
960 if scheme = 'INET' then
961 Result := inet
962 else
963 if scheme = 'INET4' then
964 Result := inet4
965 else
966 if scheme = 'INET6' then
967 Result := inet6
968 else
969 if scheme = 'XNET' then
970 Result := xnet
971 else
972 if scheme = 'WNET' then
973 Result := wnet
974 end;
975
976 {Extracts the Database Connect string from a Create Database Statement}
977
978 {$IF declared(TRegexpr)}
979 function ExtractConnectString(const CreateSQL: AnsiString;
980 var ConnectString: AnsiString): boolean;
981 var RegexObj: TRegExpr;
982 begin
983 RegexObj := TRegExpr.Create;
984 try
985 {extact database file spec}
986 RegexObj.ModifierG := false; {turn off greedy matches}
987 RegexObj.ModifierI := true; {case insensitive match}
988 RegexObj.Expression := '^ *CREATE +(DATABASE|SCHEMA) +''(.*)''';
989 Result := RegexObj.Exec(CreateSQL);
990 if Result then
991 ConnectString := RegexObj.Match[2];
992 finally
993 RegexObj.Free;
994 end;
995 end;
996
997 function ParseConnectString(ConnectString: AnsiString; var ServerName,
998 DatabaseName: AnsiString; var Protocol: TProtocolAll; var PortNo: AnsiString
999 ): boolean;
1000
1001 var RegexObj: TRegExpr;
1002 begin
1003 ServerName := '';
1004 DatabaseName := ConnectString;
1005 PortNo := '';
1006 Protocol := unknownProtocol;
1007 RegexObj := TRegExpr.Create;
1008 try
1009 {extact database file spec}
1010 RegexObj.ModifierG := false; {turn off greedy matches}
1011 RegexObj.Expression := '^([a-zA-Z46]+)://([a-zA-Z0-9\-\.]*)(|:[0-9a-zA-Z\-]+)/(.*)$';
1012 Result := RegexObj.Exec(ConnectString);
1013 if Result then
1014 begin
1015 {URL type connect string}
1016 Protocol := SchemeToProtocol(RegexObj.Match[1]);
1017 ServerName := RegexObj.Match[2];
1018 if RegexObj.MatchLen[3] > 0 then
1019 PortNo := system.Copy(ConnectString,RegexObj.MatchPos[3]+1,RegexObj.MatchLen[3]-1);
1020 DatabaseName := RegexObj.Match[4];
1021 if ServerName = '' then
1022 DatabaseName := '/' + DatabaseName;
1023 end
1024 else
1025 begin
1026 {URL type connect string - local loop}
1027 RegexObj.Expression := '^([a-zA-Z46]+)://(.*)$';
1028 Result := RegexObj.Exec(ConnectString);
1029 if Result then
1030 begin
1031 Protocol := SchemeToProtocol(RegexObj.Match[1]);
1032 DatabaseName := RegexObj.Match[2];
1033 end
1034 else
1035 begin
1036 RegexObj.Expression := '^([a-zA-Z]:\\.*)';
1037 Result := RegexObj.Exec(ConnectString);
1038 if Result then
1039 Protocol := Local {Windows with leading drive ID}
1040 else
1041 begin
1042 RegexObj.Expression := '^([a-zA-Z0-9\-\.]+)(|/[0-9a-zA-Z\-]+):(.*)$';
1043 Result := RegexObj.Exec(ConnectString);
1044 if Result then
1045 begin
1046 {Legacy TCP Format}
1047 ServerName := RegexObj.Match[1];
1048 if RegexObj.MatchLen[2] > 0 then
1049 PortNo := system.Copy(ConnectString,RegexObj.MatchPos[2]+1,RegexObj.MatchLen[2]-1);
1050 DatabaseName := RegexObj.Match[3];
1051 Protocol := TCP;
1052 end
1053 else
1054 begin
1055 RegexObj.Expression := '^\\\\([a-zA-Z0-9\-\.]+)(|@[0-9a-zA-Z\-]+)\\(.*)$';
1056 Result := RegexObj.Exec(ConnectString);
1057 if Result then
1058 begin
1059 {Netbui}
1060 ServerName := RegexObj.Match[1];
1061 if RegexObj.MatchLen[2] > 0 then
1062 PortNo := system.Copy(ConnectString,RegexObj.MatchPos[2]+1,RegexObj.MatchLen[2]-1);
1063 DatabaseName := RegexObj.Match[3];
1064 Protocol := NamedPipe
1065 end
1066 else
1067 begin
1068 Result := true;
1069 Protocol := Local; {Assume local}
1070 end;
1071 end;
1072 end;
1073 end;
1074 end;
1075 finally
1076 RegexObj.Free;
1077 end;
1078 end;
1079
1080 {$ELSE}
1081 {$IF declared(TRegex)}
1082 function ExtractConnectString(const CreateSQL: AnsiString;
1083 var ConnectString: AnsiString): boolean;
1084 var Regex: TRegEx;
1085 Match: TMatch;
1086 begin
1087 Regex := TRegEx.Create('^ *CREATE +(DATABASE|SCHEMA) +''(.*)''',[roIgnoreCase]);
1088 {extact database file spec}
1089 Match := Regex.Match(CreateSQL);
1090 Result := Match.Success and (Match.Groups.Count = 3);
1091 if Result then
1092 ConnectString := Match.Groups[2].Value;
1093 end;
1094
1095 function ParseConnectString(ConnectString: AnsiString; var ServerName,
1096 DatabaseName: AnsiString; var Protocol: TProtocolAll; var PortNo: AnsiString
1097 ): boolean;
1098
1099 var Regex: TRegEx;
1100 Match: TMatch;
1101 begin
1102 ServerName := '';
1103 DatabaseName := ConnectString;
1104 PortNo := '';
1105 Protocol := unknownProtocol;
1106 {extact database file spec}
1107 Match := Regex.Match(ConnectString,'^([a-zA-Z46]+)://([a-zA-Z0-9\-\.]*)(|:[0-9a-zA-Z\-]+)/(.*)$',[roIgnoreCase]);
1108 Result := Match.Success and (Match.Groups.Count = 5);
1109 if Result then
1110 begin
1111 {URL type connect string}
1112 Protocol := SchemeToProtocol(Match.Groups[1].Value);
1113 ServerName := Match.Groups[2].Value;
1114 PortNo := Match.Groups[3].Value;
1115 DatabaseName := Match.Groups[4].Value;
1116 if ServerName = '' then
1117 DatabaseName := '/' + DatabaseName;
1118 end
1119 else
1120 begin
1121 {URL type connect string - local loop}
1122 Match := Regex.Match(ConnectString,'^([a-zA-Z46]+)://(.*)$',[roIgnoreCase]);
1123 Result := Match.Success and (Match.Groups.Count = 3);
1124 if Result then
1125 begin
1126 Protocol := SchemeToProtocol(Match.Groups[1].Value);
1127 DatabaseName := Match.Groups[2].Value;
1128 end
1129 else
1130 begin
1131 Match := Regex.Match(ConnectString,'^([a-zA-Z]:\\.*)',[roIgnoreCase]);
1132 Result := Match.Success;
1133 if Result then
1134 Protocol := Local {Windows with leading drive ID}
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 {Legacy TCP Format}
1142 ServerName := Match.Groups[1].Value;
1143 PortNo := Match.Groups[2].Value;
1144 DatabaseName := Match.Groups[3].Value;
1145 Protocol := TCP;
1146 end
1147 else
1148 begin
1149 Match := Regex.Match(ConnectString,'^\\\\([a-zA-Z0-9\-\.]+)(|@[0-9a-zA-Z\-]+)\\(.*)$',[roIgnoreCase]);
1150 Result := Match.Success and (Match.Groups.Count = 4);
1151 if Result then
1152 begin
1153 {Netbui}
1154 ServerName := Match.Groups[1].Value;
1155 PortNo := Match.Groups[2].Value;
1156 DatabaseName := Match.Groups[3].Value;
1157 Protocol := NamedPipe
1158 end
1159 else
1160 begin
1161 Result := true;
1162 Protocol := Local; {Assume local}
1163 end;
1164 end;
1165 end;
1166 end;
1167 end;
1168 end;
1169 {$ELSE}
1170 {cruder version of above for Delphi < XE. Older versions lack regular expression
1171 handling.}
1172 function ExtractConnectString(const CreateSQL: AnsiString;
1173 var ConnectString: AnsiString): boolean;
1174 var i: integer;
1175 begin
1176 Result := false;
1177 i := Pos('''',CreateSQL);
1178 if i > 0 then
1179 begin
1180 ConnectString := CreateSQL;
1181 delete(ConnectString,1,i);
1182 i := Pos('''',ConnectString);
1183 if i > 0 then
1184 begin
1185 delete(ConnectString,i,Length(ConnectString)-i+1);
1186 Result := true;
1187 end;
1188 end;
1189 end;
1190
1191 function ParseConnectString(ConnectString: AnsiString;
1192 var ServerName, DatabaseName: AnsiString; var Protocol: TProtocolAll;
1193 var PortNo: AnsiString): boolean;
1194 begin
1195 Result := false;
1196 end;
1197
1198 {$IFEND}
1199 {$IFEND}
1200
1201 function GetProtocol(ConnectString: AnsiString): TProtocolAll;
1202 var ServerName,
1203 DatabaseName: AnsiString;
1204 PortNo: AnsiString;
1205 begin
1206 if not ParseConnectString(ConnectString,ServerName,DatabaseName,Result,PortNo) then
1207 Result := unknownProtocol;
1208 end;
1209
1210 {Make a connect string in format appropriate protocol}
1211
1212 function MakeConnectString(ServerName, DatabaseName: AnsiString;
1213 Protocol: TProtocol; PortNo: AnsiString): AnsiString;
1214
1215 function FormatURL: AnsiString;
1216 begin
1217 if (ServerName = '') and (Pos('/',DatabaseName) <= 1) then
1218 Result := DatabaseName
1219 else
1220 Result := ServerName + '/' + DatabaseName;
1221 end;
1222
1223 begin
1224 if ServerName = '' then ServerName := 'localhost';
1225 if PortNo <> '' then
1226 case Protocol of
1227 NamedPipe:
1228 ServerName := ServerName + '@' + PortNo;
1229 Local,
1230 SPX,
1231 xnet: {do nothing};
1232 TCP:
1233 ServerName := ServerName + '/' + PortNo;
1234 else
1235 ServerName := ServerName + ':' + PortNo;
1236 end;
1237
1238 case Protocol of
1239 TCP: Result := ServerName + ':' + DatabaseName; {do not localize}
1240 SPX: Result := ServerName + '@' + DatabaseName; {do not localize}
1241 NamedPipe: Result := '\\' + ServerName + '\' + DatabaseName; {do not localize}
1242 Local: Result := DatabaseName; {do not localize}
1243 inet: Result := 'inet://' + FormatURL; {do not localize}
1244 inet4: Result := 'inet4://' + FormatURL; {do not localize}
1245 inet6: Result := 'inet6://' + FormatURL; {do not localize}
1246 wnet: Result := 'wnet://' + FormatURL; {do not localize}
1247 xnet: Result := 'xnet://' + FormatURL; {do not localize}
1248 end;
1249 end;
1250
1251 {Format an SQL Identifier according to SQL Dialect with encapsulation if necessary}
1252
1253 function QuoteIdentifierIfNeeded(Dialect: Integer; Value: AnsiString): AnsiString;
1254 begin
1255 Value := TrimRight(Value);
1256 if (Dialect = 3) and
1257 (IsReservedWord(Value) or not IsSQLIdentifier(Value) or (AnsiUpperCase(Value) <> Value)) then
1258 Result := '"' + StringReplace (TrimRight(Value), '"', '""', [rfReplaceAll]) + '"'
1259 else
1260 Result := Value
1261 end;
1262
1263 {Replaces unknown characters in a string with underscores}
1264
1265 function Space2Underscore(s: AnsiString): AnsiString;
1266 var
1267 k: integer;
1268 begin
1269 Result := s;
1270 for k := 1 to Length(s) do
1271 if not (Result[k] in ValidSQLIdentifierChars) then
1272 Result[k] := '_';
1273 end;
1274
1275 {Reformats an SQL string with single quotes duplicated.}
1276
1277 function SQLSafeString(const s: AnsiString): AnsiString;
1278 begin
1279 Result := StringReplace(s,'''','''''',[rfReplaceAll]);
1280 end;
1281
1282 { TSQLParamProcessor }
1283
1284 function TSQLParamProcessor.DoExecute(GenerateParamNames: boolean;
1285 var slNames: TStrings): AnsiString;
1286 var token: TSQLTokens;
1287 iParamSuffix: Integer;
1288 begin
1289 Result := '';
1290 iParamSuffix := 0;
1291
1292 while not EOF do
1293 begin
1294 token := GetNextToken;
1295 case token of
1296 sqltParam,
1297 sqltQuotedParam:
1298 begin
1299 Result := Result + '?';
1300 slNames.Add(TokenText);
1301 end;
1302
1303 sqltPlaceHolder:
1304 if GenerateParamNames then
1305 begin
1306 Inc(iParamSuffix);
1307 slNames.AddObject(sIBXParam + IntToStr(iParamSuffix),self); //Note local convention
1308 //add pointer to self to mark entry
1309 Result := Result + '?';
1310 end
1311 else
1312 IBError(ibxeSQLParseError, [SParamNameExpected]);
1313
1314 sqltQuotedString:
1315 Result := Result + '''' + SQLSafeString(TokenText) + '''';
1316
1317 sqltIdentifierInDoubleQuotes:
1318 Result := Result + '"' + StringReplace(TokenText,'"','""',[rfReplaceAll]) + '"';
1319
1320 sqltComment:
1321 Result := Result + '/*' + TokenText + '*/';
1322
1323 sqltCommentLine:
1324 Result := Result + '--' + TokenText + LineEnding;
1325
1326 sqltEOL:
1327 Result := Result + LineEnding;
1328
1329 else
1330 Result := Result + TokenText;
1331 end;
1332 end;
1333 end;
1334
1335 function TSQLParamProcessor.GetChar: AnsiChar;
1336 begin
1337 if FIndex <= Length(FInString) then
1338 begin
1339 Result := FInString[FIndex];
1340 Inc(FIndex);
1341 end
1342 else
1343 Result := #0;
1344 end;
1345
1346 class function TSQLParamProcessor.Execute(sSQL: AnsiString;
1347 GenerateParamNames: boolean; var slNames: TStrings): AnsiString;
1348 begin
1349 with self.Create do
1350 try
1351 FInString := sSQL;
1352 FIndex := 1;
1353 Result := DoExecute(GenerateParamNames,slNames);
1354 finally
1355 Free;
1356 end;
1357 end;
1358
1359 { TSQLwithNamedParamsTokeniser }
1360
1361 procedure TSQLwithNamedParamsTokeniser.Assign(source: TSQLTokeniser);
1362 begin
1363 inherited Assign(source);
1364 if source is TSQLwithNamedParamsTokeniser then
1365 begin
1366 FState := TSQLwithNamedParamsTokeniser(source).FState;
1367 FNested := TSQLwithNamedParamsTokeniser(source).FNested;
1368 end;
1369 end;
1370
1371 procedure TSQLwithNamedParamsTokeniser.Reset;
1372 begin
1373 inherited Reset;
1374 FState := stInit;
1375 FNested := 0;
1376 end;
1377
1378 function TSQLwithNamedParamsTokeniser.TokenFound(var token: TSQLTokens
1379 ): boolean;
1380 begin
1381 Result := inherited TokenFound(token);
1382 if not Result then Exit;
1383
1384 case FState of
1385 stInit:
1386 begin
1387 case token of
1388 sqltColon:
1389 begin
1390 FState := stInParam;
1391 ResetQueue(token);
1392 end;
1393
1394 sqltBegin:
1395 begin
1396 FState := stInBlock;
1397 FNested := 1;
1398 end;
1399
1400 sqltOpenSquareBracket:
1401 FState := stInArrayDim;
1402
1403 end;
1404 end;
1405
1406 stInParam:
1407 begin
1408 case token of
1409 sqltIdentifier:
1410 token := sqltParam;
1411
1412 sqltIdentifierInDoubleQuotes:
1413 token := sqltQuotedParam;
1414
1415 else
1416 begin
1417 QueueToken(token);
1418 ReleaseQueue(token);
1419 end;
1420 end;
1421 FState := stInit;
1422 end;
1423
1424 stInBlock:
1425 begin
1426 case token of
1427 sqltBegin,
1428 sqltCase:
1429 Inc(FNested);
1430
1431 sqltEnd:
1432 begin
1433 Dec(FNested);
1434 if FNested = 0 then
1435 FState := stInit;
1436 end;
1437 end;
1438 end;
1439
1440 stInArrayDim:
1441 begin
1442 if token = sqltCloseSquareBracket then
1443 FState := stInit;
1444 end;
1445 end;
1446
1447 Result := (FState <> stInParam);
1448 end;
1449
1450 { TSQLTokeniser }
1451
1452 function TSQLTokeniser.GetNext: TSQLTokens;
1453 var C: AnsiChar;
1454 begin
1455 if EOF then
1456 Result := sqltEOF
1457 else
1458 begin
1459 C := GetChar;
1460 case C of
1461 #0:
1462 Result := sqltEOF;
1463 ' ',TAB:
1464 Result := sqltSpace;
1465 '0'..'9':
1466 Result := sqltNumberString;
1467 ';':
1468 Result := sqltSemiColon;
1469 '?':
1470 Result := sqltPlaceholder;
1471 '|':
1472 Result := sqltPipe;
1473 '"':
1474 Result := sqltDoubleQuotes;
1475 '''':
1476 Result := sqltSingleQuotes;
1477 '/':
1478 Result := sqltForwardSlash;
1479 '\':
1480 Result := sqltBackslash;
1481 '*':
1482 Result := sqltAsterisk;
1483 '(':
1484 Result := sqltOpenBracket;
1485 ')':
1486 Result := sqltCloseBracket;
1487 ':':
1488 Result := sqltColon;
1489 ',':
1490 Result := sqltComma;
1491 '.':
1492 Result := sqltPeriod;
1493 '=':
1494 Result := sqltEquals;
1495 '[':
1496 Result := sqltOpenSquareBracket;
1497 ']':
1498 Result := sqltCloseSquareBracket;
1499 '-':
1500 Result := sqltMinus;
1501 '<':
1502 Result := sqltLT;
1503 '>':
1504 Result := sqltGT;
1505 CR:
1506 Result := sqltCR;
1507 LF:
1508 Result := sqltEOL;
1509 else
1510 if C in ValidSQLIdentifierChars then
1511 Result := sqltIdentifier
1512 else
1513 Result := sqltOtherCharacter;
1514 end;
1515 FLastChar := C
1516 end;
1517 FNextToken := Result;
1518 end;
1519
1520 procedure TSQLTokeniser.PopQueue(var token: TSQLTokens);
1521 begin
1522 if FQFirst = FQLast then
1523 IBError(ibxeTokenQueueUnderflow,[]);
1524 token := FTokenQueue[FQFirst].token;
1525 FString := FTokenQueue[FQFirst].text;
1526 Inc(FQFirst);
1527 if FQFirst = FQLast then
1528 FQueueState := tsHold;
1529 end;
1530
1531 procedure TSQLTokeniser.Assign(source: TSQLTokeniser);
1532 begin
1533 FString := source.FString;
1534 FNextToken := source.FNextToken;
1535 FTokenQueue := source.FTokenQueue;
1536 FQueueState := source.FQueueState;
1537 FQFirst := source.FQFirst;
1538 FQLast := source.FQLast;
1539 end;
1540
1541 function TSQLTokeniser.TokenFound(var token: TSQLTokens): boolean;
1542 begin
1543 Result := (FState = stDefault);
1544 if Result and (token = sqltIdentifier) then
1545 FindReservedWord(FString,token);
1546 end;
1547
1548 procedure TSQLTokeniser.QueueToken(token: TSQLTokens; text: AnsiString);
1549 begin
1550 if FQLast > TokenQueueMaxSize then
1551 IBError(ibxeTokenQueueOverflow,[]);
1552 FTokenQueue[FQLast].token := token;
1553 FTokenQueue[FQLast].text := text;
1554 Inc(FQLast);
1555 end;
1556
1557 procedure TSQLTokeniser.QueueToken(token: TSQLTokens);
1558 begin
1559 QueueToken(token,TokenText);
1560 end;
1561
1562 procedure TSQLTokeniser.ResetQueue;
1563 begin
1564 FQFirst := 0;
1565 FQLast := 0;
1566 FQueueState := tsHold;
1567 end;
1568
1569 procedure TSQLTokeniser.ResetQueue(token: TSQLTokens; text: AnsiString);
1570 begin
1571 ResetQueue;
1572 QueueToken(token,text);
1573 end;
1574
1575 procedure TSQLTokeniser.ResetQueue(token: TSQLTokens);
1576 begin
1577 ResetQueue;
1578 QueueToken(token);
1579 end;
1580
1581 procedure TSQLTokeniser.ReleaseQueue(var token: TSQLTokens);
1582 begin
1583 FQueueState := tsRelease;
1584 PopQueue(token);
1585 end;
1586
1587 procedure TSQLTokeniser.ReleaseQueue;
1588 begin
1589 FQueueState := tsRelease;
1590 end;
1591
1592 function TSQLTokeniser.GetQueuedText: AnsiString;
1593 var i: integer;
1594 begin
1595 Result := '';
1596 for i := FQFirst to FQLast do
1597 Result := Result + FTokenQueue[i].text;
1598 end;
1599
1600 procedure TSQLTokeniser.SetTokenText(text: AnsiString);
1601 begin
1602 FString := text;
1603 end;
1604
1605 constructor TSQLTokeniser.Create;
1606 begin
1607 inherited Create;
1608 Reset;
1609 end;
1610
1611 destructor TSQLTokeniser.Destroy;
1612 begin
1613 Reset;
1614 inherited Destroy;
1615 end;
1616
1617 procedure TSQLTokeniser.Reset;
1618 begin
1619 FNextToken := sqltInit;
1620 FState := stDefault;
1621 FString := '';
1622 FEOF := false;
1623 ResetQueue;
1624 end;
1625
1626 function TSQLTokeniser.ReadCharacters(NumOfChars: integer): AnsiString;
1627 var i: integer;
1628 begin
1629 Result := FLastChar;
1630 for i := 2 to NumOfChars do
1631 begin
1632 if GetNext = sqltEOF then Exit;
1633 Result := Result + FLastChar;
1634 end;
1635 GetNext;
1636 end;
1637
1638 function TSQLTokeniser.GetNextToken: TSQLTokens;
1639 begin
1640 if FQueueState = tsRelease then
1641 repeat
1642 PopQueue(Result);
1643 FEOF := Result = sqltEOF;
1644 if TokenFound(Result) then
1645 Exit;
1646 until FQueueState <> tsRelease;
1647
1648 Result := InternalGetNextToken;
1649 end;
1650
1651 {a simple lookahead one algorithm to extra the next symbol}
1652
1653 function TSQLTokeniser.InternalGetNextToken: TSQLTokens;
1654 var C: AnsiChar;
1655 begin
1656 Result := sqltEOF;
1657
1658 if FNextToken = sqltInit then
1659 GetNext;
1660
1661 repeat
1662 if FSkipNext then
1663 begin
1664 FSkipNext := false;
1665 GetNext;
1666 end;
1667
1668 Result := FNextToken;
1669 C := FLastChar;
1670 GetNext;
1671
1672 if (Result = sqltCR) and (FNextToken = sqltEOL) then
1673 begin
1674 FSkipNext := true;
1675 Result := sqltEOL;
1676 C := LF;
1677 end;
1678
1679 case FState of
1680 stInComment:
1681 begin
1682 if (Result = sqltAsterisk) and (FNextToken = sqltForwardSlash) then
1683 begin
1684 FState := stDefault;
1685 Result := sqltComment;
1686 GetNext;
1687 end
1688 else
1689 if Result = sqltEOL then
1690 FString := FString + LineEnding
1691 else
1692 FString := FString + C;
1693 end;
1694
1695 stInCommentLine:
1696 begin
1697 case Result of
1698 sqltEOL:
1699 begin
1700 FState := stDefault;
1701 Result := sqltCommentLine;
1702 end;
1703
1704 else
1705 FString := FString + C;
1706 end;
1707 end;
1708
1709 stSingleQuoted:
1710 begin
1711 if (Result = sqltSingleQuotes) then
1712 begin
1713 if (FNextToken = sqltSingleQuotes) then
1714 begin
1715 FSkipNext := true;
1716 FString := FString + C;
1717 end
1718 else
1719 begin
1720 Result := sqltQuotedString;
1721 FState := stDefault;
1722 end;
1723 end
1724 else
1725 if Result = sqltEOL then
1726 FString := FString + LineEnding
1727 else
1728 FString := FString + C;
1729 end;
1730
1731 stDoubleQuoted:
1732 begin
1733 if (Result = sqltDoubleQuotes) then
1734 begin
1735 if (FNextToken = sqltDoubleQuotes) then
1736 begin
1737 FSkipNext := true;
1738 FString := FString + C;
1739 end
1740 else
1741 begin
1742 Result := sqltIdentifierInDoubleQuotes;
1743 FState := stDefault;
1744 end;
1745 end
1746 else
1747 if Result = sqltEOL then
1748 FString := FString + LineEnding
1749 else
1750 FString := FString + C;
1751 end;
1752
1753 stInIdentifier:
1754 begin
1755 FString := FString + C;
1756 Result := sqltIdentifier;
1757 if not (FNextToken in [sqltIdentifier,sqltNumberString]) then
1758 FState := stDefault
1759 end;
1760
1761 stInNumeric:
1762 begin
1763 FString := FString + C;
1764 if (Result = sqltPeriod) and (FNextToken = sqltPeriod) then
1765 begin
1766 {malformed decimal}
1767 FState := stInIdentifier;
1768 Result := sqltIdentifier
1769 end
1770 else
1771 begin
1772 if not (FNextToken in [sqltNumberString,sqltPeriod]) then
1773 FState := stDefault;
1774 Result := sqltNumberString;
1775 end;
1776 end;
1777
1778 else {stDefault}
1779 begin
1780 FString := C;
1781 case Result of
1782
1783 sqltPipe:
1784 if FNextToken = sqltPipe then
1785 begin
1786 Result := sqltConcatSymbol;
1787 FString := C + FLastChar;
1788 GetNext;
1789 end;
1790
1791 sqltForwardSlash:
1792 begin
1793 if FNextToken = sqltAsterisk then
1794 begin
1795 FString := '';
1796 GetNext;
1797 FState := stInComment;
1798 end
1799 end;
1800
1801 sqltMinus:
1802 begin
1803 if FNextToken = sqltMinus then
1804 begin
1805 FString := '';
1806 GetNext;
1807 FState := stInCommentLine;
1808 end;
1809 end;
1810
1811 sqltSingleQuotes:
1812 begin
1813 FString := '';
1814 FState := stSingleQuoted;
1815 end;
1816
1817 sqltDoubleQuotes:
1818 begin
1819 FString := '';
1820 FState := stDoubleQuoted;
1821 end;
1822
1823 sqltIdentifier:
1824 if FNextToken in [sqltIdentifier,sqltNumberString] then
1825 FState := stInIdentifier;
1826
1827 sqltNumberString:
1828 if FNextToken in [sqltNumberString,sqltPeriod] then
1829 FState := stInNumeric;
1830
1831 sqltEOL:
1832 FString := LineEnding;
1833 end;
1834 end;
1835 end;
1836
1837 // writeln(FString);
1838 FEOF := Result = sqltEOF;
1839 until TokenFound(Result) or EOF;
1840 end;
1841
1842 function ParseDateTimeTZString(aDateTimeStr: Ansistring; var aDateTime: TDateTime;
1843 var aTimezone: AnsiString; TimeOnly: boolean): boolean;
1844 {$IF declared(TFormatSettings)}
1845 begin
1846 {$IF declared(DefaultFormatSettings)}
1847 Result := ParseDateTimeTZString(aDateTimeStr,aDateTime,aTimeZone,DefaultFormatSettings,TimeOnly);
1848 {$ELSE}
1849 {$IF declared(FormatSettings)}
1850 Result := ParseDateTimeTZString(aDateTimeStr,aDateTime,aTimeZone,FormatSettings,TimeOnly);
1851 {$IFEND} {$IFEND}
1852 end;
1853
1854 function ParseDateTimeTZString(aDateTimeStr: Ansistring; var aDateTime: TDateTime;
1855 var aTimezone: AnsiString; aFormatSettings: TFormatSettings; TimeOnly: boolean=false): boolean;
1856 {$IFEND}
1857 const
1858 whitespacechars = [' ',#$09,#$0A,#$0D];
1859 var i,j,l: integer;
1860 aTime: TDateTime;
1861 DMs: longint;
1862 begin
1863 Result := false;
1864 aTimezone := '';
1865 if aDateTimeStr <> '' then
1866 {$if declared(TFormatSettings)}
1867 with aFormatSettings do
1868 {$IFEND}
1869 begin
1870 aDateTime := 0;
1871 {Parse to get time zone info}
1872 i := 1;
1873 while (i <= length(aDateTimeStr)) and (aDateTimeStr[i] in whitespacechars) do inc(i); {skip white space}
1874 if not TimeOnly then
1875 begin
1876 {decode date}
1877 j := i;
1878 while (j <= length(aDateTimeStr)) and (aDateTimeStr[j] in ['0'..'9',DateSeparator]) do inc(j);
1879 if TryStrToDate(system.copy(aDateTimeStr,i,j-i),aDateTime) then
1880 i := j; {otherwise start again i.e. assume time only}
1881 end;
1882
1883 while (i <= length(aDateTimeStr)) and (aDateTimeStr[i] in whitespacechars) do inc(i); {skip white space}
1884 {decode time}
1885 j := i;
1886 while (j <= length(aDateTimeStr)) and (aDateTimeStr[j] in ['0'..'9',TimeSeparator]) do inc(j);
1887 Result := TryStrToTime(system.copy(aDateTimeStr,i,j-i),aTime);
1888 if not Result then Exit;
1889 aDateTime := aDateTime + aTime;
1890 i := j;
1891
1892 {is there a factional second part}
1893 if (i <= length(aDateTimeStr)) and (aDateTimeStr[i] = '.') then
1894 begin
1895 inc(i);
1896 inc(j);
1897 while (j <= Length(aDateTimeStr)) and (aDateTimeStr[j] in ['0'..'9']) do inc(j);
1898 if j > i then
1899 begin
1900 l := j-i;
1901 if l > 4 then l := 4;
1902 Result := TryStrToInt(system.copy(aDateTimeStr,i,l),DMs);
1903 if not Result then Exit;
1904
1905 {adjust for number of significant digits}
1906 case l of
1907 3: DMs := DMs * 10;
1908 2: DMs := DMs * 100;
1909 1: DMs := DMs * 1000;
1910 end;
1911 aDateTime := aDateTime + (DMs / (MsecsPerDay*10));
1912 end;
1913 end;
1914 i := j;
1915
1916 while (i <= length(aDateTimeStr)) and (aDateTimeStr[i] in whitespacechars) do inc(i); {skip white space}
1917 {decode time zone}
1918 if i < length(aDateTimeStr) then
1919 begin
1920 j := i;
1921 while (j <= length(aDateTimeStr)) and not (aDateTimeStr[j] in whitespacechars) do inc(j);
1922 aTimezone := system.copy(aDateTimeStr,i,j-i);
1923 end;
1924 Result := true;
1925 end
1926 end;
1927
1928 {The following is similar to FPC DecodeTime except that the Firebird standard
1929 decimilliseconds is used instead of milliseconds for fractional seconds}
1930
1931 procedure FBDecodeTime(aTime: TDateTime; var Hour, Minute, Second: word;
1932 var DeciMillisecond: cardinal);
1933 var D : Double;
1934 l : cardinal;
1935 begin
1936 {conversion to decimilliseconds hacked from FPC DateTimeToTimeStamp}
1937 D := aTime * MSecsPerDay *10;
1938 if D < 0 then
1939 D := D - 0.5
1940 else
1941 D := D + 0.5;
1942 {rest hacked from FPC DecodeTIme}
1943 l := Abs(Trunc(D)) Mod (MSecsPerDay*10);
1944 Hour := l div 36000000;
1945 l := l mod 36000000;
1946 Minute := l div 600000;
1947 l := l mod 600000;
1948 Second := l div 10000;
1949 DeciMillisecond := l mod 10000;
1950 end;
1951
1952 {The following is similar to FPC EncodeTime except that the Firebird standard
1953 decimilliseconds is used instead of milliseconds for fractional seconds}
1954
1955 function FBEncodeTime(Hour, Minute, Second, DeciMillisecond: cardinal): TDateTime;
1956 const DMSecsPerDay = MSecsPerDay*10;
1957 var DMs: cardinal;
1958 D: Double;
1959 begin
1960 if (Hour<24) and (Minute<60) and (Second<60) and (DeciMillisecond<10000) then
1961 begin
1962 DMs := Hour*36000000+Minute*600000+Second*10000+DeciMillisecond;
1963 D := DMs/DMSecsPerDay;
1964 Result:=TDateTime(d)
1965 end
1966 else
1967 IBError(ibxeBadTimeSpecification,[Hour, Minute, Second, DeciMillisecond]);
1968 end;
1969
1970 {The following is similar to FPC FormatDateTime except that it additionally
1971 allows the timstamp to have a fractional seconds component with a resolution
1972 of four decimal places. This is appended to the result for FormatDateTime
1973 if the format string contains a "zzzz' string.}
1974
1975 function FBFormatDateTime(fmt: AnsiString; aDateTime: TDateTime): AnsiString;
1976 var Hour, Minute, Second: word;
1977 DeciMillisecond: cardinal;
1978 begin
1979 if Pos('zzzz',fmt) > 0 then
1980 begin
1981 FBDecodeTime(aDateTime, Hour, Minute, Second, DeciMillisecond);
1982 fmt := StringReplace(fmt, 'zzzz', Format('%.4d',[DeciMillisecond]), [rfReplaceAll]);
1983 end;
1984 Result := FormatDateTime(fmt,aDateTime);
1985 end;
1986
1987 function FormatTimeZoneOffset(EffectiveTimeOffsetMins: integer): AnsiString;
1988 begin
1989 if EffectiveTimeOffsetMins > 0 then
1990 Result := Format('+%.2d:%.2d',[EffectiveTimeOffsetMins div 60,abs(EffectiveTimeOffsetMins mod 60)])
1991 else
1992 Result := Format('%.2d:%.2d',[EffectiveTimeOffsetMins div 60,abs(EffectiveTimeOffsetMins mod 60)]);
1993 end;
1994
1995 function DecodeTimeZoneOffset(TZOffset: AnsiString; var dstOffset: integer): boolean;
1996 var i: integer;
1997 begin
1998 Result := false;
1999 TZOffset := Trim(TZOffset);
2000 for i := 1 to Length(TZOffset) do
2001 if not (TZOffset[i] in ['0'..'9','-','+',':']) then Exit;
2002
2003 Result := true;
2004 i := Pos(':',TZOffset);
2005 if i > 0 then
2006 dstOffset := StrToInt(copy(TZOffset,1,i-1)) * 60 + StrToInt(copy(TZOffset,i + 1))
2007 else
2008 dstOffset := StrToInt(TZOffset) * 60;
2009 end;
2010
2011 function StripLeadingZeros(Value: AnsiString): AnsiString;
2012 var i: Integer;
2013 start: integer;
2014 begin
2015 Result := '';
2016 start := 1;
2017 if (Length(Value) > 0) and (Value[1] = '-') then
2018 begin
2019 Result := '-';
2020 start := 2;
2021 end;
2022 for i := start to Length(Value) do
2023 if Value[i] <> '0' then
2024 begin
2025 Result := Result + system.copy(Value, i, MaxInt);
2026 Exit;
2027 end;
2028 end;
2029
2030 function StringToHex(octetString: string; MaxLineLength: integer): string; overload;
2031
2032 function ToHex(aValue: byte): string;
2033 const
2034 HexChars: array [0..15] of char = '0123456789ABCDEF';
2035 begin
2036 Result := HexChars[aValue shr 4] +
2037 HexChars[(aValue and $0F)];
2038 end;
2039
2040 var i, j: integer;
2041 begin
2042 i := 1;
2043 Result := '';
2044 if MaxLineLength = 0 then
2045 while i <= Length(octetString) do
2046 begin
2047 Result := Result + ToHex(byte(octetString[i]));
2048 Inc(i);
2049 end
2050 else
2051 while i <= Length(octetString) do
2052 begin
2053 for j := 1 to MaxLineLength do
2054 begin
2055 if i > Length(octetString) then
2056 Exit
2057 else
2058 Result := Result + ToHex(byte(octetString[i]));
2059 inc(i);
2060 end;
2061 Result := Result + LineEnding;
2062 end;
2063 end;
2064
2065 procedure StringToHex(octetString: string; TextOut: TStrings; MaxLineLength: integer); overload;
2066 begin
2067 TextOut.Add(StringToHex(octetString,MaxLineLength));
2068 end;
2069
2070 { TSQLXMLReader }
2071
2072 function TSQLXMLReader.FindTag(tag: AnsiString; var xmlTag: TXMLTag): boolean;
2073 var i: TXMLTag;
2074 begin
2075 Result := false;
2076 for i := xtBlob to xtElt do
2077 if XMLTagDefs[i].TagValue = tag then
2078 begin
2079 xmlTag := XMLTagDefs[i].XMLTag;
2080 Result := true;
2081 break;
2082 end;
2083 end;
2084
2085 function TSQLXMLReader.GetArrayData(index: integer): TArrayData;
2086 begin
2087 if (index < 0) or (index > ArrayDataCount) then
2088 ShowError(sArrayIndexError,[index]);
2089 Result := FArrayData[index];
2090 end;
2091
2092 function TSQLXMLReader.GetArrayDataCount: integer;
2093 begin
2094 Result := Length(FArrayData);
2095 end;
2096
2097 function TSQLXMLReader.GetBlobData(index: integer): TBlobData;
2098 begin
2099 if (index < 0) or (index > BlobDataCount) then
2100 ShowError(sBlobIndexError,[index]);
2101 Result := FBlobData[index];
2102 end;
2103
2104 function TSQLXMLReader.GetBlobDataCount: integer;
2105 begin
2106 Result := Length(FBlobData);
2107 end;
2108
2109 function TSQLXMLReader.GetTagName(xmltag: TXMLTag): AnsiString;
2110 var i: TXMLTag;
2111 begin
2112 Result := 'unknown';
2113 for i := xtBlob to xtElt do
2114 if XMLTagDefs[i].XMLTag = xmltag then
2115 begin
2116 Result := XMLTagDefs[i].TagValue;
2117 Exit;
2118 end;
2119 end;
2120
2121 procedure TSQLXMLReader.ProcessAttributeValue(attrValue: AnsiString);
2122 begin
2123 case FXMLTagStack[FXMLTagIndex] of
2124 xtBlob:
2125 if FAttributeName = 'subtype' then
2126 FBlobData[FCurrentBlob].SubType := StrToInt(attrValue)
2127 else
2128 ShowError(sXMLAttributeError,[FAttributeName,attrValue]);
2129
2130 xtArray:
2131 if FAttributeName = 'sqltype' then
2132 FArrayData[FCurrentArray].SQLType := StrToInt(attrValue)
2133 else
2134 if FAttributeName = 'relation_name' then
2135 FArrayData[FCurrentArray].relationName := attrValue
2136 else
2137 if FAttributeName = 'column_name' then
2138 FArrayData[FCurrentArray].columnName := attrValue
2139 else
2140 if FAttributeName = 'dim' then
2141 FArrayData[FCurrentArray].Dim := StrToInt(attrValue)
2142 else
2143 if FAttributeName = 'length' then
2144 FArrayData[FCurrentArray].Size := StrToInt(attrValue)
2145 else
2146 if FAttributeName = 'scale' then
2147 FArrayData[FCurrentArray].Scale := StrToInt(attrValue)
2148 else
2149 if FAttributeName = 'charset' then
2150 FArrayData[FCurrentArray].CharSet := attrValue
2151 else
2152 if FAttributeName = 'bounds' then
2153 ProcessBoundsList(attrValue)
2154 else
2155 ShowError(sXMLAttributeError,[FAttributeName,attrValue]);
2156
2157 xtElt:
2158 if FAttributeName = 'ix' then
2159 with FArrayData[FCurrentArray] do
2160 Index[CurrentRow] := StrToInt(attrValue)
2161 else
2162 ShowError(sXMLAttributeError,[FAttributeName,attrValue]);
2163 end;
2164 end;
2165
2166 procedure TSQLXMLReader.ProcessBoundsList(boundsList: AnsiString);
2167 var list: TStringList;
2168 i,j: integer;
2169 begin
2170 list := TStringList.Create;
2171 try
2172 list.Delimiter := ',';
2173 list.DelimitedText := boundsList;
2174 with FArrayData[FCurrentArray] do
2175 begin
2176 if dim <> list.Count then
2177 ShowError(sInvalidBoundsList,[boundsList]);
2178 SetLength(bounds,dim);
2179 for i := 0 to list.Count - 1 do
2180 begin
2181 j := Pos(':',list[i]);
2182 if j = 0 then
2183 raise Exception.CreateFmt(sInvalidBoundsList,[boundsList]);
2184 bounds[i].LowerBound := StrToInt(system.copy(list[i],1,j-1));
2185 bounds[i].UpperBound := StrToInt(system.copy(list[i],j+1,length(list[i])-j));
2186 end;
2187 end;
2188 finally
2189 list.Free;
2190 end;
2191 end;
2192
2193 procedure TSQLXMLReader.ProcessTagValue(tagValue: AnsiString);
2194
2195 function nibble(hex: AnsiChar): byte;
2196 begin
2197 case hex of
2198 '0': Result := 0;
2199 '1': Result := 1;
2200 '2': Result := 2;
2201 '3': Result := 3;
2202 '4': Result := 4;
2203 '5': Result := 5;
2204 '6': Result := 6;
2205 '7': Result := 7;
2206 '8': Result := 8;
2207 '9': Result := 9;
2208 'a','A': Result := 10;
2209 'b','B': Result := 11;
2210 'c','C': Result := 12;
2211 'd','D': Result := 13;
2212 'e','E': Result := 14;
2213 'f','F': Result := 15;
2214 end;
2215 end;
2216
2217 procedure RemoveWhiteSpace(var hexData: AnsiString);
2218 var i: integer;
2219 begin
2220 {Remove White Space}
2221 i := 1;
2222 while i <= length(hexData) do
2223 begin
2224 case hexData[i] of
2225 ' ',#9,#10,#13:
2226 begin
2227 if i < Length(hexData) then
2228 Move(hexData[i+1],hexData[i],Length(hexData)-i);
2229 SetLength(hexData,Length(hexData)-1);
2230 end;
2231 else
2232 Inc(i);
2233 end;
2234 end;
2235 end;
2236
2237 procedure WriteToBlob(hexData: AnsiString);
2238 var i,j : integer;
2239 blength: integer;
2240 P: PByte;
2241 BlobBuffer: PByte;
2242 begin
2243 RemoveWhiteSpace(hexData);
2244 if odd(length(hexData)) then
2245 ShowError(sBinaryBlockMustbeEven,[nil]);
2246 blength := Length(hexData) div 2;
2247 {$ifdef FPC}
2248 BlobBuffer := GetMem(blength);
2249 {$else}
2250 GetMem(BlobBuffer,blength);
2251 {$endif}
2252 try
2253 j := 1;
2254 P := BlobBuffer;
2255 for i := 1 to blength do
2256 begin
2257 P^ := (nibble(hexData[j]) shl 4) or nibble(hexdata[j+1]);
2258 Inc(j,2);
2259 Inc(P);
2260 end;
2261 FBlobData[FCurrentBlob].BlobIntf.Write(BlobBuffer^,blength);
2262 finally
2263 FreeMem(BlobBuffer);
2264 end;
2265 end;
2266
2267 begin
2268 if tagValue = '' then Exit;
2269 case FXMLTagStack[FXMLTagIndex] of
2270 xtBlob:
2271 WriteToBlob(tagValue);
2272
2273 xtElt:
2274 with FArrayData[FCurrentArray] do
2275 ArrayIntf.SetAsString(index,tagValue);
2276
2277 end;
2278 end;
2279
2280 procedure TSQLXMLReader.XMLTagInit(xmltag: TXMLTag);
2281 begin
2282 if FXMLTagIndex > MaxXMLTags then
2283 ShowError(sXMLStackOverFlow,[nil]);
2284 Inc(FXMLTagIndex);
2285 FXMLTagStack[FXMLTagIndex] := xmltag;
2286 FXMLString := '';
2287
2288 case xmltag of
2289 xtBlob:
2290 begin
2291 Inc(FCurrentBlob);
2292 SetLength(FBlobData,FCurrentBlob+1);
2293 FBlobData[FCurrentBlob].BlobIntf := nil;
2294 FBlobData[FCurrentBlob].SubType := 0;
2295 end;
2296
2297 xtArray:
2298 begin
2299 Inc(FCurrentArray);
2300 SetLength(FArrayData,FCurrentArray+1);
2301 with FArrayData[FCurrentArray] do
2302 begin
2303 ArrayIntf := nil;
2304 SQLType := 0;
2305 dim := 0;
2306 Size := 0;
2307 Scale := 0;
2308 CharSet := 'NONE';
2309 SetLength(Index,0);
2310 CurrentRow := -1;
2311 end;
2312 end;
2313
2314 xtElt:
2315 with FArrayData[FCurrentArray] do
2316 Inc(CurrentRow)
2317 end;
2318 end;
2319
2320 function TSQLXMLReader.XMLTagEnd(var xmltag: TXMLTag): boolean;
2321 begin
2322 if FXMLTagIndex = 0 then
2323 ShowError(sXMLStackUnderflow,[nil]);
2324
2325 xmlTag := FXMLTagStack[FXMLTagIndex];
2326 case FXMLTagStack[FXMLTagIndex] of
2327 xtBlob:
2328 FBlobData[FCurrentBlob].BlobIntf.Close;
2329
2330 xtArray:
2331 FArrayData[FCurrentArray].ArrayIntf.SaveChanges;
2332
2333 xtElt:
2334 Dec(FArrayData[FCurrentArray].CurrentRow);
2335 end;
2336 Dec(FXMLTagIndex);
2337 Result := FXMLTagIndex = 0;
2338 end;
2339
2340 procedure TSQLXMLReader.XMLTagEnter;
2341 var aCharSetID: integer;
2342 begin
2343 if (Attachment = nil) or not Attachment.IsConnected then
2344 ShowError(sNoDatabase);
2345 if Transaction = nil then
2346 ShowError(sNoTransaction);
2347 case FXMLTagStack[FXMLTagIndex] of
2348 xtBlob:
2349 begin
2350 if not Transaction.InTransaction then
2351 Transaction.Start;
2352 FBlobData[FCurrentBlob].BlobIntf := Attachment.CreateBlob(
2353 Transaction,FBlobData[FCurrentBlob].SubType);
2354 end;
2355
2356 xtArray:
2357 with FArrayData[FCurrentArray] do
2358 begin
2359 if not Transaction.InTransaction then
2360 Transaction.Start;
2361 Attachment.CharSetName2CharSetID(CharSet,aCharSetID);
2362 SetLength(Index,dim);
2363 ArrayIntf := Attachment.CreateArray(
2364 Transaction,
2365 Attachment.CreateArrayMetaData(SQLType,
2366 relationName,columnName,Scale,Size,
2367 aCharSetID,dim,bounds)
2368 );
2369 end;
2370 end;
2371 end;
2372
2373 {This is where the XML tags are identified and the token stream modified in
2374 consequence}
2375
2376 function TSQLXMLReader.TokenFound(var token: TSQLTokens): boolean;
2377
2378 procedure NotAnXMLTag;
2379 begin
2380 begin
2381 if FXMLTagIndex = 0 then
2382 {nothing to do with XML so go back to processing SQL}
2383 begin
2384 QueueToken(token);
2385 ReleaseQueue(token);
2386 FXMLState := stNoXML
2387 end
2388 else
2389 begin
2390 {Not an XML tag, so just push back to XML Data}
2391 FXMLState := stXMLData;
2392 FXMLString := FXMLString + GetQueuedText;
2393 ResetQueue;
2394 end;
2395 end;
2396 end;
2397
2398 var XMLTag: TXMLTag;
2399 begin
2400 Result := inherited TokenFound(token);
2401 if not Result then Exit;
2402
2403 case FXMLState of
2404 stNoXML:
2405 if token = sqltLT then
2406 begin
2407 ResetQueue;
2408 QueueToken(token); {save in case this is not XML}
2409 FXMLState := stInTag;
2410 end;
2411
2412 stInTag:
2413 {Opening '<' found, now looking for tag name or end tag marker}
2414 case token of
2415 sqltBlob,
2416 sqltIdentifier:
2417 begin
2418 if FindTag(TokenText,XMLTag) then
2419 begin
2420 XMLTagInit(XMLTag);
2421 QueueToken(token);
2422 FXMLState := stInTagBody;
2423 end
2424 else
2425 NotAnXMLTag;
2426 end;
2427
2428 sqltForwardSlash:
2429 FXMLState := stInEndTag;
2430
2431 else
2432 NotAnXMLTag;
2433 end {case token};
2434
2435 stInTagBody:
2436 {Tag name found. Now looking for attribute or closing '>'}
2437 case token of
2438 sqltIdentifier:
2439 begin
2440 FAttributeName := TokenText;
2441 QueueToken(token);
2442 FXMLState := stAttribute;
2443 end;
2444
2445 sqltGT:
2446 begin
2447 ResetQueue;
2448 XMLTagEnter;
2449 FXMLState := stXMLData;
2450 end;
2451
2452 sqltSpace,
2453 sqltEOL:
2454 QueueToken(token);
2455
2456 else
2457 NotAnXMLTag;
2458 end {case token};
2459
2460 stAttribute:
2461 {Attribute name found. Must be followed by an '=', a '>' or another tag name}
2462 case token of
2463 sqltEquals:
2464 begin
2465 QueueToken(token);
2466 FXMLState := stAttributeValue;
2467 end;
2468
2469 sqltSpace,
2470 sqltEOL:
2471 QueueToken(token);
2472
2473 sqltIdentifier:
2474 begin
2475 ProcessAttributeValue('');
2476 FAttributeName := TokenText;
2477 QueueToken(token);
2478 FXMLState := stAttribute;
2479 end;
2480
2481 sqltGT:
2482 begin
2483 ProcessAttributeValue('');
2484 ResetQueue;
2485 XMLTagEnter;
2486 FXMLState := stXMLData;
2487 end;
2488
2489 else
2490 NotAnXMLTag;
2491 end; {case token}
2492
2493 stAttributeValue:
2494 {Looking for attribute value as a single identifier or a double quoted value}
2495 case token of
2496 sqltIdentifier,sqltIdentifierInDoubleQuotes:
2497 begin
2498 ProcessAttributeValue(TokenText);
2499 QueueToken(token);
2500 FXMLState := stInTagBody;
2501 end;
2502
2503 sqltSpace,
2504 sqltEOL:
2505 QueueToken(token);
2506
2507 else
2508 NotAnXMLTag;
2509 end; {case token}
2510
2511 stXMLData:
2512 if token = sqltLT then
2513 begin
2514 QueueToken(token); {save in case this is not XML}
2515 FXMLState := stInTag;
2516 end
2517 else
2518 FXMLString := FXMLString + TokenText;
2519
2520 stInEndTag:
2521 {Opening '</' found, now looking for tag name}
2522 case token of
2523 sqltBlob,
2524 sqltIdentifier:
2525 begin
2526 if FindTag(TokenText,XMLTag) and (XMLTag = FXMLTagStack[FXMLTagIndex]) then
2527 begin
2528 QueueToken(token);
2529 FXMLState := stInEndTagBody;
2530 end
2531 else
2532 ShowError(sInvalidEndTag,[TokenText]);
2533 end;
2534 else
2535 NotAnXMLTag;
2536 end {case token};
2537
2538 stInEndTagBody:
2539 {End tag name found, now looping for closing '>'}
2540 case Token of
2541 sqltGT:
2542 begin
2543 ProcessTagValue(FXMLString);
2544 if XMLTagEnd(XMLTag) then
2545 begin
2546 ResetQueue;
2547 QueueToken(sqltColon,':');
2548 case XMLTag of
2549 xtBlob:
2550 QueueToken(sqltIdentifier,Format(ibx_blob+'%d',[FCurrentBlob]));
2551
2552 xtArray:
2553 QueueToken(sqltIdentifier, Format(ibx_array+'%d',[FCurrentArray]));
2554 end;
2555 ReleaseQueue(token);
2556 FXMLState := stNoXML;
2557 end
2558 else
2559 FXMLState := stXMLData;
2560 end;
2561
2562 sqltSpace,
2563 sqltEOL:
2564 QueueToken(token);
2565
2566 else
2567 ShowError(sBadEndTagClosing);
2568 end; {case token}
2569
2570 end {Case FState};
2571
2572 {Only allow token to be returned if not processing an XML tag}
2573
2574 Result := FXMLState = stNoXML;
2575 end;
2576
2577 procedure TSQLXMLReader.ShowError(msg: AnsiString; params: array of const);
2578 begin
2579 raise EIBClientError.CreateFmt(GetErrorPrefix + msg,params);
2580 end;
2581
2582 procedure TSQLXMLReader.ShowError(msg: AnsiString);
2583 begin
2584 ShowError(msg,[nil]);
2585 end;
2586
2587 constructor TSQLXMLReader.Create;
2588 begin
2589 inherited;
2590 FXMLState := stNoXML;
2591 end;
2592
2593 destructor TSQLXMLReader.Destroy;
2594 begin
2595 Reset;
2596 inherited Destroy;
2597 end;
2598
2599 procedure TSQLXMLReader.FreeDataObjects;
2600 begin
2601 FXMLTagIndex := 0;
2602 SetLength(FBlobData,0);
2603 FCurrentBlob := -1;
2604 SetLength(FArrayData,0);
2605 FCurrentArray := -1;
2606 end;
2607
2608 class function TSQLXMLReader.FormatBlob(Field: ISQLData): AnsiString;
2609 begin
2610 Result := FormatBlob(Field.AsString,Field.getSubtype);
2611 end;
2612
2613 class function TSQLXMLReader.FormatBlob(contents: AnsiString; subtype: integer
2614 ): AnsiString;
2615 var TextOut: TStrings;
2616 begin
2617 TextOut := TStringList.Create;
2618 try
2619 TextOut.Add(Format('<blob subtype="%d">',[subtype]));
2620 StringToHex(contents,TextOut,BlobLineLength);
2621 TextOut.Add('</blob>');
2622 Result := TextOut.Text;
2623 finally
2624 TextOut.Free;
2625 end;
2626 end;
2627
2628
2629 class function TSQLXMLReader.FormatArray(ar: IArray
2630 ): AnsiString;
2631 var index: array of integer;
2632 TextOut: TStrings;
2633
2634 procedure AddElements(dim: integer; indent:AnsiString = ' ');
2635 var i: integer;
2636 recurse: boolean;
2637 begin
2638 SetLength(index,dim+1);
2639 recurse := dim < ar.GetDimensions - 1;
2640 with ar.GetBounds[dim] do
2641 for i := LowerBound to UpperBound do
2642 begin
2643 index[dim] := i;
2644 if recurse then
2645 begin
2646 TextOut.Add(Format('%s<elt id="%d">',[indent,i]));
2647 AddElements(dim+1,indent + ' ');
2648 TextOut.Add('</elt>');
2649 end
2650 else
2651 if ((ar.GetSQLType = SQL_TEXT) or (ar.GetSQLType = SQL_VARYING)) and
2652 (ar.GetCharSetID = 1) then
2653 TextOut.Add(Format('%s<elt ix="%d">%s</elt>',[indent,i,StringToHex(ar.GetAsString(index))]))
2654 else
2655 TextOut.Add(Format('%s<elt ix="%d">%s</elt>',[indent,i,ar.GetAsString(index)]));
2656 end;
2657 end;
2658
2659 var
2660 s: AnsiString;
2661 bounds: TArrayBounds;
2662 i: integer;
2663 boundsList: AnsiString;
2664 begin
2665 TextOut := TStringList.Create;
2666 try
2667 if ar.GetCharSetWidth = 0 then
2668 s := Format('<array dim = "%d" sqltype = "%d" length = "%d" relation_name = "%s" column_name = "%s"',
2669 [ar.GetDimensions,ar.GetSQLType,ar.GetSize,
2670 ar.GetTableName,ar.GetColumnName])
2671 else
2672 s := Format('<array dim = "%d" sqltype = "%d" length = "%d" relation_name = "%s" column_name = "%s"',
2673 [ar.GetDimensions,ar.GetSQLType,ar.GetSize div ar.GetCharSetWidth,
2674 ar.GetTableName,ar.GetColumnName]);
2675 case ar.GetSQLType of
2676 SQL_DOUBLE, SQL_FLOAT, SQL_LONG, SQL_SHORT, SQL_D_FLOAT, SQL_INT64:
2677 s := s + Format(' scale = "%d"',[ ar.GetScale]);
2678 SQL_TEXT,
2679 SQL_VARYING:
2680 s := s + Format(' charset = "%s"',[ar.GetAttachment.GetCharsetName(ar.GetCharSetID)]);
2681 end;
2682 bounds := ar.GetBounds;
2683 boundsList := '';
2684 for i := 0 to length(bounds) - 1 do
2685 begin
2686 if i <> 0 then boundsList := boundsList + ',';
2687 boundsList := boundsList + Format('%d:%d',[bounds[i].LowerBound,bounds[i].UpperBound]);
2688 end;
2689 s := s + Format(' bounds="%s"',[boundsList]);
2690 s := s + '>';
2691 TextOut.Add(s);
2692
2693 SetLength(index,0);
2694 AddElements(0);
2695 TextOut.Add('</array>');
2696 Result := TextOut.Text;
2697 finally
2698 TextOut.Free;
2699 end;
2700 end;
2701
2702 procedure TSQLXMLReader.Reset;
2703 begin
2704 inherited Reset;
2705 FreeDataObjects;
2706 FXMLString := '';
2707 end;
2708
2709 { TCustomJournalProcessor }
2710
2711 procedure TCustomJournalProcessor.DoExecute;
2712 var token: TSQLTokens;
2713 LineState: TLineState;
2714 JnlEntry: TJnlEntry;
2715 Len: integer;
2716 tz: AnsiString;
2717
2718 procedure ClearJnlEntry;
2719 begin
2720 LineState := lsInit;
2721 with JnlEntry do
2722 begin
2723 TransactionName := '';
2724 TPB := nil;
2725 QueryText :='';
2726 JnlEntryType := jeUnknown;
2727 SessionID := 0;
2728 TransactionID := 0;
2729 DefaultCompletion := taCommit;
2730 end;
2731 end;
2732
2733 function CreateTPB(TPBText: AnsiString): ITPB;
2734 var index: integer;
2735 begin
2736 Result := nil;
2737 if Length(TPBText) = 0 then
2738 Exit;
2739 Result := FFirebirdClientAPI.AllocateTPB;
2740 try
2741 index := Pos('[',TPBText);
2742 if index > 0 then
2743 system.Delete(TPBText,1,index);
2744 repeat
2745 index := Pos(',',TPBText);
2746 if index = 0 then
2747 begin
2748 index := Pos(']',TPBText);
2749 if index <> 0 then
2750 system.Delete(TPBText,index,1);
2751 Result.AddByTypeName(TPBText);
2752 break;
2753 end;
2754 Result.AddByTypeName(system.copy(TPBText,1,index-1));
2755 system.Delete(TPBText,1,index);
2756 until false;
2757 except
2758 Result := nil;
2759 raise;
2760 end;
2761 end;
2762
2763 begin
2764 ClearJnlEntry;
2765 while not EOF do
2766 begin
2767 token := GetNextToken;
2768 with JnlEntry do
2769 case token of
2770 sqltAsterisk:
2771 if LineState = lsInit then
2772 LineState := lsJnlFound;
2773
2774 sqltIdentifier:
2775 if LineState = lsJnlFound then
2776 begin
2777 JnlEntryType := IdentifyJnlEntry(TokenText);
2778 LineState := lsGotJnlType;
2779 end
2780 else
2781 ClearJnlEntry;
2782
2783 sqltQuotedString:
2784 if (LineState = lsGotJnlType)
2785 and ParseDateTimeTZString(TokenText,TimeStamp,tz) then
2786 LineState := lsGotTimestamp
2787 else
2788 ClearJnlEntry;
2789
2790 sqltColon:
2791 case LineState of
2792 lsGotText1Length:
2793 begin
2794 if Len > 0 then
2795 begin
2796 if JnlEntryType = jeTransStart then
2797 TransactionName := ReadCharacters(Len)
2798 else
2799 QueryText := ReadCharacters(Len)
2800 end;
2801 if JnlEntryType = jeTransStart then
2802 LineState := lsGotText1
2803 else
2804 begin
2805 DoNextJournalEntry(JnlEntry);
2806 ClearJnlEntry;
2807 end
2808 end;
2809
2810 lsGotText2Length:
2811 begin
2812 if Len > 0 then
2813 TPB := CreateTPB(ReadCharacters(Len));
2814 LineState := lsGotText2;
2815 end;
2816
2817 else
2818 if LineState <> lsGotJnlType then
2819 ClearJnlEntry;
2820 end;
2821
2822 sqltComma:
2823 if not (LineState in [lsGotTimestamp,lsGotAttachmentID,lsGotSessionID,lsGotTransactionID,lsGotText1,lsGotText2]) then
2824 ClearJnlEntry;
2825
2826 sqltNumberString:
2827 case LineState of
2828 lsGotTimestamp:
2829 begin
2830 AttachmentID := StrToInt(TokenText);
2831 LineState := lsGotAttachmentID;
2832 end;
2833
2834 lsGotAttachmentID:
2835 begin
2836 SessionID := StrToInt(TokenText);
2837 LineState := lsGotSessionID;
2838 end;
2839
2840 lsGotSessionID:
2841 begin
2842 TransactionID := StrToInt(TokenText);
2843 if JnlEntryType in [jeTransCommit, jeTransCommitFail, jeTransRollback, jeTransRollbackFail] then
2844 begin
2845 DoNextJournalEntry(JnlEntry);
2846 ClearJnlEntry;
2847 end
2848 else
2849 LineState := lsGotTransactionID;
2850 end;
2851
2852 lsGotTransactionID:
2853 begin
2854 case JnlEntryType of
2855 jeTransStart:
2856 begin
2857 len := StrToInt(TokenText);
2858 LineState := lsGotText1Length;
2859 end;
2860
2861 jeQuery:
2862 begin
2863 len := StrToInt(TokenText);
2864 LineState := lsGotText1Length;
2865 end;
2866
2867 jeTransCommitRet,
2868 jeTransCommitFail,
2869 jeTransRollbackFail,
2870 jeTransRollbackRet:
2871 begin
2872 OldTransactionID := StrToInt(TokenText);
2873 DoNextJournalEntry(JnlEntry);
2874 ClearJnlEntry;
2875 end;
2876
2877 else
2878 ClearJnlEntry;
2879 end; {case JnlEntryType}
2880
2881 end;
2882
2883 lsGotText1:
2884 begin
2885 len := StrToInt(TokenText);
2886 LineState := lsGotText2Length;
2887 end;
2888
2889 lsGotText2:
2890 begin
2891 if JnlEntryType = jeTransStart then
2892 begin
2893 DefaultCompletion := TTransactionCompletion(StrToInt(TokenText));
2894 DoNextJournalEntry(JnlEntry);
2895 end;
2896 ClearJnlEntry;
2897 end;
2898 end; {case LineState}
2899 end; {case token}
2900 end; {while}
2901 ClearJnlEntry;
2902 end;
2903
2904 function TCustomJournalProcessor.IdentifyJnlEntry(aTokenText: AnsiString
2905 ): TJnlEntryType;
2906 begin
2907 Result := jeUnknown;
2908 if Length(aTokenText) > 0 then
2909 case aTokenText[1] of
2910 'S':
2911 Result := jeTransStart;
2912 'C':
2913 Result := jeTransCommit;
2914 'c':
2915 Result := jeTransCommitRet;
2916 'R':
2917 Result := jeTransRollback;
2918 'r':
2919 Result := jeTransRollbackRet;
2920 'Q':
2921 Result := jeQuery;
2922 'F':
2923 Result := jeTransCommitFail;
2924 'f':
2925 Result := jeTransRollbackFail;
2926 end;
2927 end;
2928
2929 procedure TCustomJournalProcessor.DoNextJournalEntry(JnlEntry: TJnlEntry);
2930 begin
2931 if assigned(FOnNextJournalEntry) then
2932 FOnNextJournalEntry(JnlEntry);
2933 end;
2934
2935 constructor TCustomJournalProcessor.Create(api: IFirebirdAPI);
2936 begin
2937 inherited Create;
2938 FFirebirdClientAPI := api;
2939 end;
2940
2941 class function TCustomJournalProcessor.JnlEntryText(je: TJnlEntryType): string;
2942 begin
2943 case je of
2944 jeTransStart:
2945 Result := 'Transaction Start';
2946 jeTransCommit:
2947 Result := 'Commit';
2948 jeTransCommitFail:
2949 Result := 'Commit (Failed)';
2950 jeTransCommitRet:
2951 Result := 'Commit Retaining';
2952 jeTransRollback:
2953 Result := 'Rollback';
2954 jeTransRollbackFail:
2955 Result := 'Rollback (Failed)';
2956 jeTransRollbackRet:
2957 Result := 'Rollback Retaining';
2958 jeQuery:
2959 Result := 'Query';
2960 jeUnknown:
2961 Result := 'Unknown';
2962 end;
2963 end;
2964
2965 {TJournalProcessor}
2966
2967 function TJournalProcessor.GetChar: AnsiChar;
2968 begin
2969 if FInStream.Read(Result,1) = 0 then
2970 Result := #0;
2971 end;
2972
2973 destructor TJournalProcessor.Destroy;
2974 begin
2975 FInStream.Free;
2976 inherited Destroy;
2977 end;
2978
2979 class procedure TJournalProcessor.Execute(aFileName: string; api: IFirebirdAPI;
2980 aOnNextJournalEntry: TOnNextJournalEntry);
2981 begin
2982 Execute(TFileStream.Create(aFileName,fmOpenRead),api,aOnNextJournalEntry);
2983 end;
2984
2985 class procedure TJournalProcessor.Execute(S: TStream; api: IFirebirdAPI;
2986 aOnNextJournalEntry: TOnNextJournalEntry);
2987 begin
2988 with TJournalProcessor.Create(api) do
2989 try
2990 FInStream := S;
2991 OnNextJournalEntry := aOnNextJournalEntry;
2992 DoExecute;
2993 finally
2994 Free
2995 end;
2996 end;
2997
2998
2999 end.

Properties

Name Value
svn:eol-style native