ViewVC Help
View File | Revision Log | Show Annotations | Download File | View Changeset | Root Listing
root/public/ibx/trunk/runtime/ibxscript.pas
Revision: 80
Committed: Mon Jan 1 11:31:07 2018 UTC (6 years, 3 months ago) by tony
Content type: text/x-pascal
File size: 59953 byte(s)
Log Message:
Fixes merged into public release

File Contents

# Content
1 (*
2 * IBX For Lazarus (Firebird Express)
3 *
4 * The contents of this file are subject to the Initial Developer's
5 * Public License Version 1.0 (the "License"); you may not use this
6 * file except in compliance with the License. You may obtain a copy
7 * of the License here:
8 *
9 * http://www.firebirdsql.org/index.php?op=doc&id=idpl
10 *
11 * Software distributed under the License is distributed on an "AS
12 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
13 * implied. See the License for the specific language governing rights
14 * and limitations under the License.
15 *
16 * The Initial Developer of the Original Code is Tony Whyman.
17 *
18 * The Original Code is (C) 2014-2017 Tony Whyman, MWA Software
19 * (http://www.mwasoftware.co.uk).
20 *
21 * All Rights Reserved.
22 *
23 * Contributor(s): ______________________________________.
24 *
25 *)
26 unit ibxscript;
27
28 {$mode objfpc}{$H+}
29
30 {$codepage UTF8}
31
32 interface
33
34 uses Classes, IBDatabase, IBSQL, IB, IBDataOutput;
35
36 const
37 ibx_blob = 'IBX_BLOB';
38 ibx_array = 'IBX_ARRAY';
39
40 BlobLineLength = 40;
41
42 {Non-character symbols}
43 sqNone = #0;
44 sqEnd = #1;
45 sqBegin = #2;
46 sqString = #3;
47 sqComment = #4;
48 sqCase = #5;
49 sqDeclare = #6;
50 sqCommentLine = #7;
51 sqEOL = #8;
52 sqTab = #9;
53 sqTerminator = #10;
54 sqEOF = #11;
55 sqTag = #12;
56 sqEndTag = #13;
57 sqQuotedString = #14;
58 sqDoubleQuotedString = #15;
59
60 type
61 TSQLSymbol = char;
62
63 TSQLStates = (stInit, stError, stInSQL, stNested, stInDeclaration);
64
65 TXMLStates = (stInTag,stAttribute,stAttributeValue,stQuotedAttributeValue,
66 stTagged,stEndTag);
67
68 TXMLTag = (xtNone,xtBlob,xtArray,xtElt);
69
70 TOnNextLine = procedure(Sender: TObject; Line: string) of object;
71 TOnProgressEvent = procedure (Sender: TObject; Reset: boolean; value: integer) of object;
72
73 TXMLTagDef = record
74 XMLTag: TXMLTag;
75 TagValue: string;
76 end;
77
78 const
79 XMLTagDefs: array [0..2] of TXMLTagDef = (
80 (XMLTag: xtBlob; TagValue: 'blob'),
81 (XMLTag: xtArray; TagValue: 'array'),
82 (XMLTag: xtElt; TagValue: 'elt')
83 );
84
85 type
86
87 { TSymbolStream }
88
89 {A simple lookahead one parser to process a text stream as a stream of symbols.
90 This is an abstract object, subclassed for different sources.}
91
92 TSymbolStream = class
93 private
94 FNextSymbol: TSQLSymbol;
95 FOnNextLine: TOnNextLine;
96 FOnProgressEvent: TOnProgressEvent;
97 FTerminator: char;
98 FLastChar: char;
99 FIndex: integer;
100 FLine: string;
101 FString: string;
102 FXMLTag: TXMLTag;
103 FXMLMode: integer;
104 protected
105 FNextStatement: boolean;
106 function GetErrorPrefix: string; virtual; abstract;
107 function GetNextSymbol(C: char): TSQLSymbol;
108 function FindTag(tag: string; var xmlTag: TXMLTag): boolean;
109 function GetNextLine(var Line: string):boolean; virtual; abstract;
110 public
111 constructor Create;
112 procedure ShowError(msg: string; params: array of const);
113 function GetSymbol: TSQLSymbol; virtual;
114 procedure NextStatement;
115 property SymbolValue: string read FString;
116 property Terminator: char read FTerminator write FTerminator;
117 property XMLTag: TXMLTag read FXMLTag;
118 property OnNextLine: TOnNextLine read FOnNextLine write FOnNextLine;
119 property OnProgressEvent: TOnProgressEvent read FOnProgressEvent write FOnProgressEvent; {Progress Bar Support}
120 end;
121
122 { TBatchSymbolStream }
123
124 {This symbol stream supports non-interactive parsing of a text file, stream or
125 lines of text.}
126
127 TBatchSymbolStream = class(TSymbolStream)
128 private
129 FLines: TStrings;
130 FLineIndex: integer;
131 protected
132 function GetErrorPrefix: string; override;
133 function GetNextLine(var Line: string):boolean; override;
134 public
135 constructor Create;
136 destructor Destroy; override;
137 procedure SetStreamSource(Lines: TStrings); overload;
138 procedure SetStreamSource(S: TStream); overload;
139 procedure SetStreamSource(FileName: string); overload;
140 end;
141
142 { TInteractiveSymbolStream }
143
144 {This symbol stream supports interactive parsing of commands and
145 SQL statements entered at a console}
146
147 TInteractiveSymbolStream = class(TSymbolStream)
148 private
149 FPrompt: string;
150 FContinuePrompt: string;
151 FTerminated: boolean;
152 protected
153 function GetErrorPrefix: string; override;
154 function GetNextLine(var Line: string):boolean; override;
155 public
156 constructor Create(aPrompt: string='SQL>'; aContinue: string = 'CON>');
157 function GetSymbol: TSQLSymbol; override;
158 property Terminated: boolean read FTerminated write FTerminated;
159 end;
160
161 TBlobData = record
162 BlobIntf: IBlob;
163 SubType: cardinal;
164 end;
165
166 TArrayData = record
167 ArrayIntf: IArray;
168 SQLType: cardinal;
169 relationName: string;
170 columnName: string;
171 dim: cardinal;
172 Size: cardinal;
173 Scale: integer;
174 CharSet: string;
175 bounds: TArrayBounds;
176 CurrentRow: integer;
177 Index: array of integer;
178 end;
179
180 { TIBXMLProcessor }
181
182 {This is a simple XML parser that parses the output of a symbol stream as XML
183 structured data, recognising tags, attributes and data. The tags are given in
184 the table XMLTagDefs. The BlobData and ArrayData properties return blob and
185 array data decoded from the XML stream.}
186
187 TIBXMLProcessor = class
188 private
189 FDatabase: TIBDatabase;
190 FSymbolStream: TSymbolStream;
191 FState: TXMLStates;
192 FTransaction: TIBTransaction;
193 FXMLTagStack: array [1..20] of TXMLTag;
194 FXMLTagIndex: integer;
195 FAttributeName: string;
196 FBlobData: array of TBlobData;
197 FCurrentBlob: integer;
198 FArrayData: array of TArrayData;
199 FCurrentArray: integer;
200 FBlobBuffer: PChar;
201 procedure EndXMLTag(xmltag: TXMLTag);
202 procedure EnterTag;
203 function GetArrayData(index: integer): TArrayData;
204 function GetArrayDataCount: integer;
205 function GetBlobData(index: integer): TBlobData;
206 function GetBlobDataCount: integer;
207 procedure ProcessTagValue(tagValue: string);
208 procedure StartXMLTag(xmltag: TXMLTag);
209 procedure ProcessAttributeValue(attrValue: string);
210 procedure ProcessBoundsList(boundsList: string);
211 public
212 constructor Create;
213 destructor Destroy; override;
214 function AnalyseXML(SymbolStream: TSymbolStream): string;
215 procedure NextStatement;
216 class function FormatBlob(Field: ISQLData): string;
217 class function FormatArray(Database: TIBDatabase; ar: IArray): string;
218 property BlobData[index: integer]: TBlobData read GetBlobData;
219 property BlobDataCount: integer read GetBlobDataCount;
220 property ArrayData[index: integer]: TArrayData read GetArrayData;
221 property ArrayDataCount: integer read GetArrayDataCount;
222 property Database: TIBDatabase read FDatabase write FDatabase;
223 property Transaction: TIBTransaction read FTransaction write FTransaction;
224 end;
225
226 { TIBSQLProcessor }
227
228 {This parses a symbol stream into SQL statements. If embedded XML is found then
229 this is processed by the supplied XMLProcessor. The HasBegin property provides
230 a simple way to recognised stored procedure DDL, and "Execute As" statements.}
231
232 TIBSQLProcessor = class
233 private
234 FSQLText: string;
235 FState: TSQLStates;
236 FStack: array [0..16] of TSQLStates;
237 FStackindex: integer;
238 FHasBegin: boolean;
239 FInCase: boolean;
240 FNested: integer;
241 FXMLProcessor: TIBXMLProcessor;
242 FSymbolStream: TSymbolStream;
243 procedure AddToSQL(const Symbol: string);
244 procedure SetState(AState: TSQLStates);
245 function PopState: TSQLStates;
246 public
247 constructor Create(XMLProcessor: TIBXMLProcessor);
248 function GetNextStatement(SymbolStream: TSymbolStream; var stmt: string) : boolean;
249 property HasBegin: boolean read FHasBegin;
250 end;
251
252 TGetParamValue = procedure(Sender: TObject; ParamName: string; var BlobID: TISC_QUAD) of object;
253 TLogEvent = procedure(Sender: TObject; Msg: string) of Object;
254 TOnSelectSQL = procedure (Sender: TObject; SQLText: string) of object;
255 TOnSetStatement = procedure(Sender: TObject; command, aValue, stmt: string; var Done: boolean) of object;
256 TOnCreateDatabase = procedure (Sender: TObject; var DatabaseFileName: string) of object;
257
258 { TCustomIBXScript }
259
260 {This is the main script processing engine and can be customised by subclassing
261 and defining the symbol stream appropriate for use.
262
263 The RunScript function is used to invoke the processing of a symbol stream. Each
264 SQL statement is extracted one by one. If it is recognised as a built in command
265 by "ProcessStatement" then it is actioned directly. Otherwise, it is executed
266 using the TIBSQL component. Note that SQL validation by this class is only partial
267 and is sufficient only to parse the SQL into statements. The Firebird engine does
268 the rest when the statement is executed.}
269
270 TCustomIBXScript = class(TComponent)
271 private
272 FEcho: boolean;
273 FIBXMLProcessor: TIBXMLProcessor;
274 FIBSQLProcessor: TIBSQLProcessor;
275 FDatabase: TIBDatabase;
276 FDataOutputFormatter: TIBCustomDataOutput;
277 FIgnoreGrants: boolean;
278 FOnCreateDatabase: TOnCreateDatabase;
279 FOnErrorLog: TLogEvent;
280 FOnSelectSQL: TOnSelectSQL;
281 FOnSetStatement: TOnSetStatement;
282 FShowAffectedRows: boolean;
283 FShowPerformanceStats: boolean;
284 FStopOnFirstError: boolean;
285 FTransaction: TIBTransaction;
286 FInternalTransaction: TIBTransaction;
287 FISQL: TIBSQL;
288 FGetParamValue: TGetParamValue;
289 FOnOutputLog: TLogEvent;
290 FAutoDDL: boolean;
291 procedure DoCommit;
292 procedure DoReconnect;
293 procedure ExecSQL(stmt: string);
294 function GetOnProgressEvent: TOnProgressEvent;
295 function GetTransaction: TIBTransaction;
296 procedure SetDatabase(AValue: TIBDatabase);
297 procedure SetDataOutputFormatter(AValue: TIBCustomDataOutput);
298 procedure SetOnProgressEvent(AValue: TOnProgressEvent);
299 procedure SetParamValue(SQLVar: ISQLParam);
300 procedure SetShowPerformanceStats(AValue: boolean);
301 procedure SetTransaction(AValue: TIBTransaction);
302 protected
303 FSymbolStream: TSymbolStream;
304 procedure Add2Log(const Msg: string; IsError: boolean=true); virtual;
305 procedure EchoNextLine(Sender: TObject; Line: string);
306 procedure Notification(AComponent: TComponent; Operation: TOperation); override;
307 function ProcessStatement(stmt: string): boolean; virtual;
308 function ProcessStream: boolean;
309 public
310 constructor Create(aOwner: TComponent); override;
311 destructor Destroy; override;
312 procedure DefaultSelectSQLHandler(aSQLText: string);
313 published
314 property Database: TIBDatabase read FDatabase write SetDatabase;
315 property DataOutputFormatter: TIBCustomDataOutput read FDataOutputFormatter
316 write SetDataOutputFormatter;
317 property AutoDDL: boolean read FAutoDDL write FAutoDDL default true;
318 property Echo: boolean read FEcho write FEcho default true; {Echo Input to Log}
319 property IgnoreGrants: boolean read FIgnoreGrants write FIgnoreGrants;
320 property Transaction: TIBTransaction read FTransaction write SetTransaction;
321 property ShowAffectedRows: boolean read FShowAffectedRows write FShowAffectedRows;
322 property ShowPerformanceStats: boolean read FShowPerformanceStats write SetShowPerformanceStats;
323 property StopOnFirstError: boolean read FStopOnFirstError write FStopOnFirstError default true;
324 property GetParamValue: TGetParamValue read FGetParamValue write FGetParamValue; {resolve parameterized queries}
325 property OnOutputLog: TLogEvent read FOnOutputLog write FOnOutputLog; {Log handler}
326 property OnErrorLog: TLogEvent read FOnErrorLog write FOnErrorLog;
327 property OnProgressEvent: TOnProgressEvent read GetOnProgressEvent write SetOnProgressEvent; {Progress Bar Support}
328 property OnSelectSQL: TOnSelectSQL read FOnSelectSQL write FOnSelectSQL; {Handle Select SQL Statements}
329 property OnSetStatement: TOnSetStatement read FOnSetStatement write FOnSetStatement;
330 property OnCreateDatabase: TOnCreateDatabase read FOnCreateDatabase write FOnCreateDatabase;
331 end;
332
333 {
334 TIBXScript: runs an SQL script in the specified file or stream. The text is parsed
335 into SQL statements which are executed in turn. The intention is to be ISQL
336 compatible but with extensions:
337
338 * All DML and DDL Statements are supported.
339
340 * CREATE DATABASE, DROP DATABASE, CONNECT and COMMIT are supported.
341
342 * The following SET statements are supported:
343 SET SQL DIALECT
344 SET TERM
345 SET AUTODDL
346 SET BAIL
347 SET ECHO
348 SET COUNT
349 SET STATS
350 SET NAMES <character set>
351
352 * New Command: RECONNECT. Performs a commit followed by disconnecting and
353 reconnecting to the database.
354
355 * Procedure Bodies (BEGIN .. END blocks) are self-delimiting and do not need
356 an extra terminator. If a terminator is present, this is treated as an
357 empty statement. The result is ISQL compatible, but does not require the
358 use of SET TERM.
359
360 * DML statements may have arguments in IBX format (e.g UPDATE MYTABLE Set data = :mydata).
361 Arguments are valid only for BLOB columns and are resolved using the GetParamValue
362 event. This returns the blobid to be used. A typical use of the event is to
363 read binary data from a file, save it in a blob stream and return the blob id.
364
365 Select SQL statements are not directly supported but can be handled by an external
366 handler (OnSelectSQL event). If the handler is not present then an exception
367 is raised if a Select SQL statement is found.
368
369 Properties:
370
371 * Database: Link to TIBDatabase component
372 * Transaction: Link to Transaction. Defaults to internaltransaction (concurrency, wait)
373 * AutoDDL: When true DDL statements are automatically committed after execution
374 * Echo: boolean. When true, all SQL statements are echoed to log
375 * StopOnFirstError: boolean. When true the script engine terminates on the first
376 SQL Error.
377 * IgnoreGrants: When true, grant statements are silently discarded. This can be
378 useful when applying a script using the Embedded Server.
379 * ShowPerformanceStats: When true, performance statistics (in ISQL format) are
380 written to the log after a DML statement is executed
381 * DataOutputFormatter: Identifies a Data Output Formatter component used to format
382 the results of executing a Select Statement
383
384
385 Events:
386
387 * GetParamValue: called when an SQL parameter is found (in PSQL :name format).
388 This is only called for blob fields. Handler should return the BlobID to be
389 used as the parameter value. If not present an exception is raised when a
390 parameter is found.
391 * OnOutputLog: Called to write SQL Statements to the log (stdout)
392 * OnErrorLog: Called to write all other messages to the log (stderr)
393 * OnProgressEvent: Progress bar support. If Reset is true the value is maximum
394 value of progress bar. Otherwise called to step progress bar.
395 * OnSelectSQL: handler for select SQL statements. If not present, select SQL
396 statements result in an exception.
397 * OnSetStatement: called to process a SET command that has not already been
398 handled by TIBXScript.
399
400 The RunScript function is used to execute an SQL Script and may be called
401 multiple times.
402 }
403
404 { TIBXScript }
405
406 TIBXScript = class(TCustomIBXScript)
407 public
408 constructor Create(aOwner: TComponent); override;
409 {use RunScript instead of PerformUpdate}
410 function PerformUpdate(SQLFile: string; aAutoDDL: boolean): boolean; overload; deprecated;
411 function PerformUpdate(SQLStream: TStream; aAutoDDL: boolean): boolean; overload; deprecated;
412 function RunScript(SQLFile: string): boolean; overload;
413 function RunScript(SQLStream: TStream): boolean; overload;
414 function RunScript(SQLLines: TStrings): boolean; overload;
415 function ExecSQLScript(sql: string): boolean;
416 end;
417
418 function StringToHex(octetString: string; MaxLineLength: integer=0): string; overload;
419 procedure StringToHex(octetString: string; TextOut: TStrings; MaxLineLength: integer=0); overload;
420
421
422 resourcestring
423 sInvalidSetStatement = 'Invalid %s Statement - %s';
424
425 implementation
426
427 uses Sysutils, RegExpr;
428
429 resourcestring
430 sTerminatorUnknownState = 'Statement Terminator in unexpected state (%d)';
431 sUnterminatedString = 'Unterminated string';
432 sUnknownSymbol = 'Unknown Symbol %d';
433 sNoSelectSQL = 'Select SQL Statements are not supported';
434 sStackUnderflow = 'Stack Underflow';
435 sNoParamQueries = 'Parameterised Queries are not supported';
436 sStackOverFlow = 'Stack Overflow';
437 sResolveQueryParam = 'Resolving Query Parameter: %s';
438 sNoCommit = 'Commit not allowed here';
439 sNoReconnect = 'Reconnect not allowed here';
440 sXMLStackUnderflow = 'XML Stack Underflow';
441 sInvalidEndTag = 'XML End Tag Mismatch - %s';
442 sXMLStackOverFlow = 'XML Stack Overflow';
443 sErrorState = 'Entered Error State';
444 sXMLError = 'Invalid XML (%c)';
445 sXMLAttributeError = 'Unexpected attribute - "%s" = "%s"';
446 sInvalidBoundsList = 'Invalid array bounds list - "%s"';
447 sBinaryBlockMustbeEven = 'Binary block must have an even number of characters';
448 sInvalidCharacterSet = 'Unrecognised character set name - "%s"';
449 sOnLineError = 'On Line %d Character %d: ';
450 sArrayIndexError = 'Array Index Error (%d)';
451 sBlobIndexError = 'Blob Index Error (%d)';
452 sStatementError = 'Error processing SQL statement: %s %s - for statement "%s"';
453
454 function StringToHex(octetString: string; MaxLineLength: integer): string; overload;
455
456 function ToHex(aValue: byte): string;
457 const
458 HexChars: array [0..15] of char = '0123456789ABCDEF';
459 begin
460 Result := HexChars[aValue shr 4] +
461 HexChars[(aValue and $0F)];
462 end;
463
464 var i, j: integer;
465 begin
466 i := 1;
467 Result := '';
468 if MaxLineLength = 0 then
469 while i <= Length(octetString) do
470 begin
471 Result += ToHex(byte(octetString[i]));
472 Inc(i);
473 end
474 else
475 while i <= Length(octetString) do
476 begin
477 for j := 1 to MaxLineLength do
478 begin
479 if i > Length(octetString) then
480 Exit
481 else
482 Result += ToHex(byte(octetString[i]));
483 inc(i);
484 end;
485 Result += LineEnding;
486 end;
487 end;
488
489 procedure StringToHex(octetString: string; TextOut: TStrings; MaxLineLength: integer); overload;
490 begin
491 TextOut.Add(StringToHex(octetString,MaxLineLength));
492 end;
493
494
495
496 { TIBXScript }
497
498 constructor TIBXScript.Create(aOwner: TComponent);
499 begin
500 inherited Create(aOwner);
501 FSymbolStream := TBatchSymbolStream.Create;
502 FSymbolStream.OnNextLine := @EchoNextLine;
503 end;
504
505 function TIBXScript.PerformUpdate(SQLFile: string; aAutoDDL: boolean): boolean;
506 begin
507 FAutoDDL := aAutoDDL;
508 Result := RunScript( SQLFile);
509 end;
510
511 function TIBXScript.PerformUpdate(SQLStream: TStream; aAutoDDL: boolean
512 ): boolean;
513 begin
514 FAutoDDL := aAutoDDL;
515 Result := RunScript(SQLStream);
516 end;
517
518 function TIBXScript.RunScript(SQLFile: string): boolean;
519 begin
520 TBatchSymbolStream(FSymbolStream).SetStreamSource(SQLFile);
521 Result := ProcessStream;
522 end;
523
524 function TIBXScript.RunScript(SQLStream: TStream): boolean;
525 begin
526 TBatchSymbolStream(FSymbolStream).SetStreamSource(SQLStream);
527 Result := ProcessStream;
528 end;
529
530 function TIBXScript.RunScript(SQLLines: TStrings): boolean;
531 begin
532 TBatchSymbolStream(FSymbolStream).SetStreamSource(SQLLines);
533 Result := ProcessStream;
534 end;
535
536 function TIBXScript.ExecSQLScript(sql: string): boolean;
537 var s: TStringList;
538 begin
539 s := TStringList.Create;
540 try
541 s.Text := sql;
542 TBatchSymbolStream(FSymbolStream).SetStreamSource(s);
543 Result := ProcessStream;
544 finally
545 s.Free;
546 end;
547 end;
548
549 { TCustomIBXScript }
550
551 procedure TCustomIBXScript.Add2Log(const Msg: string; IsError: boolean);
552 begin
553 if IsError then
554 begin
555 if assigned(OnErrorLog) then OnErrorLog(self,Msg)
556 end
557 else
558 if assigned(FOnOutputLog) then FOnOutputLog(self,Msg)
559 end;
560
561 procedure TCustomIBXScript.DoCommit;
562 begin
563 with GetTransaction do
564 if InTransaction then Commit;
565 GetTransaction.Active := true;
566 end;
567
568 procedure TCustomIBXScript.DoReconnect;
569 begin
570 with GetTransaction do
571 if InTransaction then Commit;
572 Database.Connected := false;
573 Database.Connected := true;
574 GetTransaction.Active := true;
575 end;
576
577 procedure TCustomIBXScript.ExecSQL(stmt: string);
578 var DDL: boolean;
579 I: integer;
580 stats: TPerfCounters;
581 begin
582 Database.Connected := true;
583 FISQL.SQL.Text := stmt;
584 FISQL.Transaction := GetTransaction;
585 FISQL.Transaction.Active := true;
586 FISQL.ParamCheck := not FIBSQLProcessor.HasBegin; {Probably PSQL}
587 FISQL.Prepare;
588 FISQL.Statement.EnableStatistics(ShowPerformanceStats);
589
590 if FISQL.SQLStatementType in [SQLInsert, SQLUpdate, SQLDelete] then
591 begin
592 {Interpret parameters}
593 for I := 0 to FISQL.Params.Count - 1 do
594 SetParamValue(FISQL.Params[I]);
595 end;
596
597 if FISQL.SQLStatementType = SQLSelect then
598 begin
599 if assigned(OnSelectSQL) then
600 OnSelectSQL(self,stmt)
601 else
602 DefaultSelectSQLHandler(stmt);
603 end
604 else
605 begin
606 DDL := FISQL.SQLStatementType = SQLDDL;
607 if not DDL or not FIgnoreGrants or (Pos('GRANT',AnsiUpperCase(Trim(stmt))) <> 1) then
608 begin
609 FISQL.ExecQuery;
610 if ShowAffectedRows and not DDL then
611 Add2Log('Rows Affected: ' + IntToStr(FISQL.RowsAffected));
612 if not DDL then
613 TIBCustomDataOutput.ShowPerfStats(FISQL.Statement,@Add2Log);
614 end;
615
616 if FAutoDDL and DDL then
617 FISQL.Transaction.Commit;
618 FISQL.Close;
619 end;
620 FISQL.SQL.Clear;
621 end;
622
623 function TCustomIBXScript.GetOnProgressEvent: TOnProgressEvent;
624 begin
625 Result := FSymbolStream.OnProgressEvent;
626 end;
627
628 function TCustomIBXScript.GetTransaction: TIBTransaction;
629 begin
630 if FTransaction = nil then
631 Result := FInternalTransaction
632 else
633 Result := FTransaction;
634 end;
635
636 procedure TCustomIBXScript.EchoNextLine(Sender: TObject; Line: string);
637 begin
638 if Echo then Add2Log(Line);
639 end;
640
641 procedure TCustomIBXScript.Notification(AComponent: TComponent;
642 Operation: TOperation);
643 begin
644 inherited Notification(AComponent, Operation);
645 if (AComponent = FDatabase) and (Operation = opRemove) then
646 FDatabase := nil;
647 if (AComponent = FTransaction) and (Operation = opRemove) then
648 FTransaction := nil;
649 if (AComponent = DataOutputFormatter) and (Operation = opRemove) then
650 FDataOutputFormatter := nil;
651 end;
652
653 procedure TCustomIBXScript.SetDatabase(AValue: TIBDatabase);
654 begin
655 if FDatabase = AValue then Exit;
656 FDatabase := AValue;
657 FISQL.Database := AValue;
658 FIBXMLProcessor.Database := AValue;
659 FInternalTransaction.DefaultDatabase := AValue;
660 end;
661
662 procedure TCustomIBXScript.SetDataOutputFormatter(AValue: TIBCustomDataOutput);
663 begin
664 if FDataOutputFormatter = AValue then Exit;
665 if (FDataOutputFormatter <> nil) and (AValue <> nil) then
666 AValue.Assign(FDataOutputFormatter);
667 FDataOutputFormatter := AValue;
668 if FDataOutputFormatter <> nil then
669 FDataOutputFormatter.Database := Database;
670 end;
671
672 procedure TCustomIBXScript.SetOnProgressEvent(AValue: TOnProgressEvent);
673 begin
674 FSymbolStream.OnProgressEvent := AValue;
675 end;
676
677 procedure TCustomIBXScript.SetParamValue(SQLVar: ISQLParam);
678 var BlobID: TISC_QUAD;
679 ix: integer;
680 begin
681 if (SQLVar.SQLType = SQL_BLOB) and (Pos(ibx_blob,SQLVar.Name) = 1) then
682 begin
683 ix := StrToInt(system.copy(SQLVar.Name,length(ibx_blob)+1,length(SQLVar.Name)-length(ibx_blob)));
684 SQLVar.AsBlob := FIBXMLProcessor.BlobData[ix].BlobIntf;
685 Exit;
686 end
687 else
688 if (SQLVar.SQLType = SQL_ARRAY) and (Pos(ibx_array,SQLVar.Name) = 1) then
689 begin
690 ix := StrToInt(system.copy(SQLVar.Name,length(ibx_array)+1,length(SQLVar.Name)-length(ibx_array)));
691 SQLVar.AsArray := FIBXMLProcessor.ArrayData[ix].ArrayIntf;
692 Exit;
693 end;
694
695 if assigned(FGetParamValue) and (SQLVar.SQLType = SQL_BLOB) then
696 begin
697 Add2Log(Format(sResolveQueryParam,[SQLVar.Name]));
698 GetParamValue(self,SQLVar.Name,BlobID);
699 if (BlobID.gds_quad_high = 0) and (BlobID.gds_quad_low = 0) then
700 SQLVar.Clear
701 else
702 SQLVar.AsQuad := BlobID
703 end
704 else
705 raise Exception.Create(sNoParamQueries);
706 end;
707
708 procedure TCustomIBXScript.SetShowPerformanceStats(AValue: boolean);
709 begin
710 if FShowPerformanceStats = AValue then Exit;
711 FShowPerformanceStats := AValue;
712 if assigned(DataOutputFormatter) then
713 DataOutputFormatter.ShowPerformanceStats := AValue;
714 end;
715
716 function TCustomIBXScript.ProcessStream: boolean;
717 var stmt: string;
718 begin
719 Result := false;
720 while FIBSQLProcessor.GetNextStatement(FSymbolStream,stmt) do
721 try
722 // writeln('stmt = ',stmt);
723 if trim(stmt) = '' then continue;
724 if not ProcessStatement(stmt) then
725 ExecSQL(stmt);
726
727 except on E:Exception do
728 begin
729 if FInternalTransaction.InTransaction then
730 FInternalTransaction.Rollback;
731 if assigned(OnErrorLog) then
732 begin
733 Add2Log(Format(sStatementError,[FSymbolStream.GetErrorPrefix,
734 E.Message,stmt]),true);
735 if StopOnFirstError then Exit;
736 end
737 else
738 raise;
739 end
740 end;
741 Result := true;
742 end;
743
744 function TCustomIBXScript.ProcessStatement(stmt: string): boolean;
745 var command: string;
746 ucStmt: string;
747
748 function Toggle(aValue: string): boolean;
749 begin
750 aValue := AnsiUpperCase(aValue);
751 if aValue = 'ON' then
752 Result := true
753 else
754 if aValue = 'OFF' then
755 Result := false
756 else
757 raise Exception.CreateFmt(sInvalidSetStatement, [command,stmt]);
758 end;
759
760 procedure ExtractUserInfo;
761 var RegexObj: TRegExpr;
762 begin
763 RegexObj := TRegExpr.Create;
764 try
765 RegexObj.ModifierG := false; {turn off greedy matches}
766 RegexObj.Expression := ' +USER +''(.+)''';
767 if RegexObj.Exec(ucStmt) then
768 FDatabase.Params.Values['user_name'] := RegexObj.Match[1];
769
770 RegexObj.Expression := ' +PASSWORD +''(.+)''';
771 if RegexObj.Exec(ucStmt) then
772 FDatabase.Params.Values['password'] :=
773 system.copy(stmt,RegexObj.MatchPos[1],RegexObj.MatchLen[1]);
774 finally
775 RegexObj.Free;
776 end;
777 end;
778
779 procedure ExtractConnectInfo;
780 var RegexObj: TRegExpr;
781 begin
782 ExtractUserInfo;
783 RegexObj := TRegExpr.Create;
784 try
785 RegexObj.ModifierG := false; {turn off greedy matches}
786 RegexObj.Expression := '^ *CONNECT +''(.*)''';
787 if RegexObj.Exec(ucStmt) then
788 begin
789 FDatabase.DatabaseName := system.copy(stmt,RegexObj.MatchPos[1],RegexObj.MatchLen[1]);
790 end;
791
792 RegexObj.Expression := ' +ROLE +''(.+)''';
793 if RegexObj.Exec(ucStmt) then
794 FDatabase.Params.Values['sql_role_name'] := RegexObj.Match[1]
795 else
796 with FDatabase.Params do
797 if IndexOfName('sql_role_name') <> -1 then
798 Delete(IndexOfName('sql_role_name'));
799
800 RegexObj.Expression := ' +CACHE +([0-9]+)';
801 if RegexObj.Exec(ucStmt) then
802 FDatabase.Params.Values['cache_manager'] := RegexObj.Match[1]
803 else
804 with FDatabase.Params do
805 if IndexOfName('cache_manager') <> -1 then
806 Delete(IndexOfName('cache_manager'));
807 finally
808 RegexObj.Free;
809 end;
810 end;
811
812 procedure UpdateUserPassword;
813 var RegexObj: TRegExpr;
814 begin
815 RegexObj := TRegExpr.Create;
816 try
817 RegexObj.ModifierG := false; {turn off greedy matches}
818 RegexObj.Expression := '^ *CREATE +(DATABASE|SCHEMA) +(''.*'') +USER +''(.+)''';
819 if not RegexObj.Exec(ucStmt) and (FDatabase.Params.IndexOfName('user_name') <> -1) then
820 begin
821 RegexObj.Expression := '^ *CREATE +(DATABASE|SCHEMA) +(''.*'')';
822 if RegexObj.Exec(ucStmt) then
823 begin
824 system.Insert(' USER ''' + FDatabase.Params.Values['user_name'] +'''',stmt,
825 RegexObj.MatchPos[2] + RegexObj.MatchLen[2]);
826 ucStmt := AnsiUpperCase(stmt);
827 end;
828 end;
829
830 RegexObj.Expression := '^ *CREATE +(DATABASE|SCHEMA) +''.*'' +USER +''.+'' PASSWORD +''(.+)''';
831 if not RegexObj.Exec(ucStmt) and (FDatabase.Params.IndexOfName('password') <> -1) then
832 begin
833 RegexObj.Expression := '^ *CREATE +(DATABASE|SCHEMA) +''.*'' +(USER +''.+'')';
834 if RegexObj.Exec(ucStmt) then
835 begin
836 system.Insert(' PASSWORD ''' + FDatabase.Params.Values['password'] +'''',stmt,
837 RegexObj.MatchPos[2] + RegexObj.MatchLen[2]);
838 ucStmt := AnsiUpperCase(stmt);
839 end;
840 end;
841 finally
842 RegexObj.Free;
843 end;
844 end;
845
846 var RegexObj: TRegExpr;
847 n: integer;
848 charsetid: integer;
849 param: string;
850 Terminator: char;
851 FileName: string;
852 begin
853 Result := false;
854 ucStmt := AnsiUpperCase(stmt);
855 Terminator := FSymbolStream.Terminator;
856 RegexObj := TRegExpr.Create;
857 try
858 {process create database}
859 RegexObj.Expression := '^ *CREATE +(DATABASE|SCHEMA) +''(.*)''(.*)(\' + Terminator + '|)';
860 if RegexObj.Exec(ucStmt) then
861 begin
862 FileName := system.copy(stmt,RegexObj.MatchPos[2], RegexObj.MatchLen[2]);
863 if assigned(FOnCreateDatabase) then
864 OnCreateDatabase(self,FileName);
865 stmt := 'CREATE DATABASE ''' + FileName + '''' + system.copy(stmt,RegexObj.MatchPos[3], RegexObj.MatchLen[3]);
866 ucStmt := AnsiUpperCase(stmt);
867 UpdateUserPassword;
868 FDatabase.Connected := false;
869 FDatabase.CreateDatabase(stmt);
870 FDatabase.Connected := false;
871 ExtractUserInfo;
872 DoReconnect;
873 Result := true;
874 Exit;
875 end;
876
877 {process connect statement}
878 RegexObj.Expression := '^ *CONNECT +.*(\' + Terminator + '|)';
879 if RegexObj.Exec(ucStmt) then
880 begin
881 ExtractConnectInfo;
882 DoReconnect;
883 Result := true;
884 Exit;
885 end;
886
887 {Process Drop Database}
888 RegexObj.Expression := '^ *DROP +DATABASE *(\' + Terminator + '|)';
889 if RegexObj.Exec(ucStmt) then
890 begin
891 FDatabase.DropDatabase;
892 Result := true;
893 Exit;
894 end;
895
896 {process commit statement}
897 RegexObj.Expression := '^ *COMMIT *(\' + Terminator + '|)';
898 if RegexObj.Exec(ucStmt) then
899 begin
900 DoCommit;
901 Result := true;
902 Exit;
903 end;
904
905 {process Reconnect statement}
906 RegexObj.Expression := '^ *RECONNECT *(\' + Terminator + '|)';
907 if RegexObj.Exec(ucStmt) then
908 begin
909 DoReconnect;
910 Result := true;
911 Exit;
912 end;
913
914
915 {Process Set Term}
916 RegexObj.Expression := '^ *SET +TERM +(.) *(\' + Terminator + '|)';
917 if RegexObj.Exec(ucStmt) then
918 begin
919 FSymbolStream.Terminator := RegexObj.Match[1][1];
920 Result := true;
921 Exit;
922 end;
923
924 {process Set SQL Dialect}
925 RegexObj.Expression := '^ *SET +SQL +DIALECT +([0-9]) *(\' + Terminator + '|)';
926 if RegexObj.Exec(ucStmt) then
927 begin
928 n := StrToInt(RegexObj.Match[1]);
929 if Database.SQLDialect <> n then
930 begin
931 Database.SQLDialect := n;
932 if Database.Connected then
933 DoReconnect;
934 end;
935 Result := true;
936 Exit;
937 end;
938
939 {Process Remaining Set statements}
940 RegexObj.Expression := '^ *SET +([A-Z]+)( +[A-Z0-9]+|) *(\' + Terminator + '|)';
941 if RegexObj.Exec(ucStmt) then
942 begin
943 command := AnsiUpperCase(RegexObj.Match[1]);
944 param := trim(RegexObj.Match[2]);
945 if command = 'AUTODDL' then
946 AutoDDL := ((RegexObj.MatchLen[2] = 0) and not AutoDDL) or
947 (RegexObj.MatchLen[2] > 0) and Toggle(param)
948 else
949 if command = 'BAIL' then
950 StopOnFirstError := ((RegexObj.MatchLen[2] = 0) and not StopOnFirstError) or
951 (RegexObj.MatchLen[2] > 0) and Toggle(param)
952 else
953 if command = 'ECHO' then
954 Echo := ((RegexObj.MatchLen[2] = 0) and not Echo) or
955 (RegexObj.MatchLen[2] > 0) and Toggle(param)
956 else
957 if command = 'COUNT' then
958 ShowAffectedRows := ((RegexObj.MatchLen[2] = 0) and not ShowAffectedRows) or
959 (RegexObj.MatchLen[2] > 0) and Toggle(param)
960 else
961 if command = 'STATS' then
962 ShowPerformanceStats := ((RegexObj.MatchLen[2] = 0) and not FShowPerformanceStats) or
963 (RegexObj.MatchLen[2] > 0) and Toggle(param)
964 else
965 if command = 'NAMES' then
966 begin
967 if Database.Attachment.CharSetName2CharSetID(param,charsetid) then
968 begin
969 Database.Params.Values['lc_ctype'] := param;
970 if Database.Connected then
971 DoReconnect;
972 end
973 else
974 raise Exception.CreateFmt(sInvalidCharacterSet, [param,stmt]);
975 end
976 else
977 begin
978 if assigned(DataOutputFormatter) then
979 DataOutputFormatter.SetCommand(command,param,stmt,Result);
980 if not Result and assigned(OnSetStatement) then
981 OnSetStatement(self,command,param,stmt,Result)
982 else
983 raise Exception.CreateFmt(sInvalidSetStatement, [command,stmt]);
984 Exit;
985 end;
986 Result := true;
987 Exit;
988 end;
989
990 finally
991 RegexObj.Free;
992 end;
993 end;
994
995 procedure TCustomIBXScript.SetTransaction(AValue: TIBTransaction);
996 begin
997 if FTransaction = AValue then Exit;
998 FTransaction := AValue;
999 FIBXMLProcessor.Transaction := AValue;
1000 end;
1001
1002 constructor TCustomIBXScript.Create(aOwner: TComponent);
1003 begin
1004 inherited Create(aOwner);
1005 FStopOnFirstError := true;
1006 FEcho := true;
1007 FAutoDDL := true;
1008 FISQL := TIBSQL.Create(self);
1009 FISQL.ParamCheck := true;
1010 FIBXMLProcessor := TIBXMLProcessor.Create;
1011 FIBSQLProcessor := TIBSQLProcessor.Create(FIBXMLProcessor);
1012 FInternalTransaction := TIBTransaction.Create(self);
1013 FInternalTransaction.Params.Clear;
1014 FInternalTransaction.Params.Add('concurrency');
1015 FInternalTransaction.Params.Add('wait');
1016 end;
1017
1018 destructor TCustomIBXScript.Destroy;
1019 begin
1020 if FIBSQLProcessor <> nil then FIBSQLProcessor.Free;
1021 if FIBXMLProcessor <> nil then FIBXMLProcessor.Free;
1022 if FSymbolStream <> nil then FSymbolStream.Free;
1023 if FISQL <> nil then FISQL.Free;
1024 if FInternalTransaction <> nil then FInternalTransaction.Free;
1025 inherited Destroy;
1026 end;
1027
1028 procedure TCustomIBXScript.DefaultSelectSQLHandler(aSQLText: string);
1029 begin
1030 if assigned(DataOutputFormatter) then
1031 DataOutputFormatter.DataOut(aSQLText,@Add2Log)
1032 else
1033 FSymbolStream.ShowError(sNoSelectSQL,[nil]);
1034 end;
1035
1036 { TIBSQLProcessor }
1037
1038 procedure TIBSQLProcessor.AddToSQL(const Symbol: string);
1039 begin
1040 FSQLText := FSQLText + Symbol;
1041 // writeln('SQL = ',FSQLText);
1042 end;
1043
1044 procedure TIBSQLProcessor.SetState(AState: TSQLStates);
1045 begin
1046 if FStackIndex > 16 then
1047 FSymbolStream.ShowError(sStackOverFlow,[nil]);
1048 FStack[FStackIndex] := FState;
1049 Inc(FStackIndex);
1050 FState := AState
1051 end;
1052
1053 function TIBSQLProcessor.PopState: TSQLStates;
1054 begin
1055 if FStackIndex = 0 then
1056 FSymbolStream.ShowError(sStackUnderflow,[nil]);
1057 Dec(FStackIndex);
1058 Result := FStack[FStackIndex]
1059 end;
1060
1061 constructor TIBSQLProcessor.Create(XMLProcessor: TIBXMLProcessor);
1062 begin
1063 inherited Create;
1064 FXMLProcessor := XMLProcessor;
1065 end;
1066
1067 function TIBSQLProcessor.GetNextStatement(SymbolStream: TSymbolStream;
1068 var stmt: string): boolean;
1069 var Symbol: TSQLSymbol;
1070 NonSpace: boolean;
1071 Done: boolean;
1072 begin
1073 FSQLText := '';
1074 FState := stInit;
1075 FHasBegin := false;
1076 FSymbolStream := SymbolStream;
1077 FXMLProcessor.NextStatement;
1078 SymbolStream.NextStatement;
1079
1080 Result := true;
1081 Done := false;
1082 NonSpace := false;
1083 while not Done do
1084 with SymbolStream do
1085 begin
1086 if FState = stError then
1087 ShowError(sErrorState,[nil]);
1088 Symbol := GetSymbol;
1089 // writeln('Symbol = ',Symbol,' Value = ',SymbolValue);
1090 if not (Symbol in [' ',sqEOL]) then
1091 NonSpace := true;
1092
1093 case Symbol of
1094 sqTag:
1095 begin
1096 if FState in [stInSQL,stNested] then
1097 AddToSQL(FXMLProcessor.AnalyseXML(SymbolStream));
1098 end;
1099
1100 sqTerminator:
1101 case FState of
1102 stInit: {ignore empty statement};
1103
1104 stInSQL:
1105 Done := true;
1106
1107 stNested:
1108 AddToSQL(Terminator);
1109
1110 stInDeclaration:
1111 begin
1112 FState := PopState;
1113 AddToSQL(Terminator);
1114 end;
1115
1116 else
1117 ShowError(sTerminatorUnknownState,[FState]);
1118 end;
1119
1120 ';':
1121 begin
1122 if FState = stInDeclaration then
1123 FState := PopState;
1124 AddToSQL(';');
1125 end;
1126
1127 '*':
1128 begin
1129 AddToSQL('*');
1130 if FState = stInit then
1131 FState := stInSQL
1132 end;
1133
1134 '/':
1135 begin
1136 AddToSQL('/');
1137 if FState = stInit then
1138 FState := stInSQL
1139 end;
1140
1141 sqComment,
1142 sqQuotedString,
1143 sqDoubleQuotedString:
1144 if FState <> stInit then
1145 AddToSQL(SymbolValue);
1146
1147 sqCommentLine:
1148 if FState <> stInit then
1149 AddToSQL(SymbolValue + LineEnding);
1150
1151 sqEnd:
1152 begin
1153 AddToSQL(SymbolValue);
1154 case FState of
1155 stNested:
1156 begin
1157 if FNested = 0 then
1158 begin
1159 FState := PopState;
1160 if not FInCase then
1161 begin
1162 FState := stInit;
1163 Done := true;
1164 end
1165 else
1166 FInCase := false;
1167 end
1168 else
1169 Dec(FNested)
1170 end;
1171 {Otherwise ignore}
1172 end
1173 end;
1174
1175 sqBegin:
1176 begin
1177 FHasBegin := true;
1178 AddToSQL(SymbolValue);
1179 case FState of
1180 stNested:
1181 Inc(FNested);
1182
1183 stInSQL,
1184 stInit:
1185 SetState(stNested);
1186 end
1187 end;
1188
1189 sqCase:
1190 begin
1191 AddToSQL(SymbolValue);
1192 case FState of
1193 stNested:
1194 Inc(FNested);
1195
1196 stInSQL,
1197 stInit:
1198 begin
1199 FInCase := true;
1200 SetState(stNested);
1201 end;
1202 end
1203 end;
1204
1205 sqDeclare:
1206 begin
1207 AddToSQL(SymbolValue);
1208 if FState in [stInit,stInSQL] then
1209 SetState(stInDeclaration)
1210 end;
1211
1212 sqString:
1213 begin
1214 AddToSQL(SymbolValue);
1215 if FState = stInit then
1216 FState := stInSQL
1217 end;
1218
1219 sqEOL:
1220 begin
1221 case FState of
1222 stInit:
1223 {Do nothing};
1224 else
1225 if NonSpace then AddToSQL(LineEnding);
1226 end;
1227 end;
1228
1229 sqEOF:
1230 begin
1231 Done := true;
1232 Result := trim(FSQLText) <> '';
1233 end
1234 else
1235 if FState <> stInit then
1236 AddToSQL(Symbol);
1237 end
1238 end;
1239 stmt := FSQLText;
1240 // writeln('stmt = ',stmt);
1241 end;
1242
1243 { TIBXMLProcessor }
1244
1245 procedure TIBXMLProcessor.EndXMLTag(xmltag: TXMLTag);
1246 begin
1247 if FXMLTagIndex = 0 then
1248 FSymbolStream.ShowError(sXMLStackUnderflow,[nil]);
1249 if xmltag <> FXMLTagStack[FXMLTagIndex] then
1250 FSymbolStream.ShowError(sInvalidEndTag,[FSymbolStream.SymbolValue]);
1251
1252 case FXMLTagStack[FXMLTagIndex] of
1253 xtBlob:
1254 FBlobData[FCurrentBlob].BlobIntf.Close;
1255
1256 xtArray:
1257 FArrayData[FCurrentArray].ArrayIntf.SaveChanges;
1258
1259 xtElt:
1260 Dec(FArrayData[FCurrentArray].CurrentRow);
1261 end;
1262 Dec(FXMLTagIndex);
1263 end;
1264
1265 procedure TIBXMLProcessor.EnterTag;
1266 var aCharSetID: integer;
1267 begin
1268 case FXMLTagStack[FXMLTagIndex] of
1269 xtBlob:
1270 begin
1271 Database.Connected := true;
1272 Transaction.Active := true;
1273 FBlobData[FCurrentBlob].BlobIntf := Database.Attachment.CreateBlob(
1274 Transaction.TransactionIntf,FBlobData[FCurrentBlob].SubType);
1275 end;
1276
1277 xtArray:
1278 with FArrayData[FCurrentArray] do
1279 begin
1280 Database.Connected := true;
1281 Transaction.Active := true;
1282 Database.Attachment.CharSetName2CharSetID(CharSet,aCharSetID);
1283 SetLength(Index,dim);
1284 ArrayIntf := Database.Attachment.CreateArray(
1285 Transaction.TransactionIntf,
1286 Database.Attachment.CreateArrayMetaData(SQLType,
1287 relationName,columnName,Scale,Size,
1288 aCharSetID,dim,bounds)
1289 );
1290 end;
1291 end;
1292 end;
1293
1294 function TIBXMLProcessor.GetArrayData(index: integer): TArrayData;
1295 begin
1296 if (index < 0) or (index > ArrayDataCount) then
1297 FSymbolStream.ShowError(sArrayIndexError,[index]);
1298 Result := FArrayData[index];
1299 end;
1300
1301 function TIBXMLProcessor.GetArrayDataCount: integer;
1302 begin
1303 Result := Length(FArrayData);
1304 end;
1305
1306 function TIBXMLProcessor.GetBlobData(index: integer): TBlobData;
1307 begin
1308 if (index < 0) or (index > BlobDataCount) then
1309 FSymbolStream.ShowError(sBlobIndexError,[index]);
1310 Result := FBlobData[index];
1311 end;
1312
1313 function TIBXMLProcessor.GetBlobDataCount: integer;
1314 begin
1315 Result := Length(FBlobData);
1316 end;
1317
1318 procedure TIBXMLProcessor.ProcessTagValue(tagValue: string);
1319
1320 function nibble(hex: char): byte;
1321 begin
1322 case hex of
1323 '0': Result := 0;
1324 '1': Result := 1;
1325 '2': Result := 2;
1326 '3': Result := 3;
1327 '4': Result := 4;
1328 '5': Result := 5;
1329 '6': Result := 6;
1330 '7': Result := 7;
1331 '8': Result := 8;
1332 '9': Result := 9;
1333 'a','A': Result := 10;
1334 'b','B': Result := 11;
1335 'c','C': Result := 12;
1336 'd','D': Result := 13;
1337 'e','E': Result := 14;
1338 'f','F': Result := 15;
1339 end;
1340 end;
1341
1342 procedure RemoveWhiteSpace(var hexData: string);
1343 var i: integer;
1344 begin
1345 {Remove White Space}
1346 i := 1;
1347 while i <= length(hexData) do
1348 begin
1349 case hexData[i] of
1350 ' ',#9,#10,#13:
1351 begin
1352 if i < Length(hexData) then
1353 Move(hexData[i+1],hexData[i],Length(hexData)-i);
1354 SetLength(hexData,Length(hexData)-1);
1355 end;
1356 else
1357 Inc(i);
1358 end;
1359 end;
1360 end;
1361
1362 procedure WriteToBlob(hexData: string);
1363 var i,j : integer;
1364 blength: integer;
1365 P: PChar;
1366 begin
1367 RemoveWhiteSpace(hexData);
1368 if odd(length(hexData)) then
1369 FSymbolStream.ShowError(sBinaryBlockMustbeEven,[nil]);
1370 blength := Length(hexData) div 2;
1371 IBAlloc(FBlobBuffer,0,blength);
1372 j := 1;
1373 P := FBlobBuffer;
1374 for i := 1 to blength do
1375 begin
1376 P^ := char((nibble(hexData[j]) shl 4) or nibble(hexdata[j+1]));
1377 Inc(j,2);
1378 Inc(P);
1379 end;
1380 FBlobData[FCurrentBlob].BlobIntf.Write(FBlobBuffer^,blength);
1381 end;
1382
1383 begin
1384 if tagValue = '' then Exit;
1385 case FXMLTagStack[FXMLTagIndex] of
1386 xtBlob:
1387 WriteToBlob(tagValue);
1388
1389 xtElt:
1390 with FArrayData[FCurrentArray] do
1391 ArrayIntf.SetAsString(index,tagValue);
1392
1393 end;
1394 end;
1395
1396 procedure TIBXMLProcessor.StartXMLTag(xmltag: TXMLTag);
1397 begin
1398 if FXMLTagIndex > 19 then
1399 FSymbolStream.ShowError(sXMLStackOverFlow,[nil]);
1400 Inc(FXMLTagIndex);
1401 FXMLTagStack[FXMLTagIndex] := xmltag;
1402 case xmltag of
1403 xtBlob:
1404 begin
1405 Inc(FCurrentBlob);
1406 SetLength(FBlobData,FCurrentBlob+1);
1407 FBlobData[FCurrentBlob].BlobIntf := nil;
1408 FBlobData[FCurrentBlob].SubType := 0;
1409 end;
1410
1411 xtArray:
1412 begin
1413 Inc(FCurrentArray);
1414 SetLength(FArrayData,FCurrentArray+1);
1415 with FArrayData[FCurrentArray] do
1416 begin
1417 ArrayIntf := nil;
1418 SQLType := 0;
1419 dim := 0;
1420 Size := 0;
1421 Scale := 0;
1422 CharSet := 'NONE';
1423 SetLength(Index,0);
1424 CurrentRow := -1;
1425 end;
1426 end;
1427
1428 xtElt:
1429 with FArrayData[FCurrentArray] do
1430 Inc(CurrentRow);
1431
1432 end;
1433 end;
1434
1435 procedure TIBXMLProcessor.ProcessAttributeValue(attrValue: string);
1436 begin
1437 case FXMLTagStack[FXMLTagIndex] of
1438 xtBlob:
1439 if FAttributeName = 'subtype' then
1440 FBlobData[FCurrentBlob].SubType := StrToInt(attrValue)
1441 else
1442 FSymbolStream.ShowError(sXMLAttributeError,[FAttributeName,attrValue]);
1443
1444 xtArray:
1445 if FAttributeName = 'sqltype' then
1446 FArrayData[FCurrentArray].SQLType := StrToInt(attrValue)
1447 else
1448 if FAttributeName = 'relation_name' then
1449 FArrayData[FCurrentArray].relationName := attrValue
1450 else
1451 if FAttributeName = 'column_name' then
1452 FArrayData[FCurrentArray].columnName := attrValue
1453 else
1454 if FAttributeName = 'dim' then
1455 FArrayData[FCurrentArray].Dim := StrToInt(attrValue)
1456 else
1457 if FAttributeName = 'length' then
1458 FArrayData[FCurrentArray].Size := StrToInt(attrValue)
1459 else
1460 if FAttributeName = 'scale' then
1461 FArrayData[FCurrentArray].Scale := StrToInt(attrValue)
1462 else
1463 if FAttributeName = 'charset' then
1464 FArrayData[FCurrentArray].CharSet := attrValue
1465 else
1466 if FAttributeName = 'bounds' then
1467 ProcessBoundsList(attrValue)
1468 else
1469 FSymbolStream.ShowError(sXMLAttributeError,[FAttributeName,attrValue]);
1470
1471 xtElt:
1472 if FAttributeName = 'ix' then
1473 with FArrayData[FCurrentArray] do
1474 Index[CurrentRow] := StrToInt(attrValue)
1475 else
1476 FSymbolStream.ShowError(sXMLAttributeError,[FAttributeName,attrValue]);
1477 end;
1478 end;
1479
1480 procedure TIBXMLProcessor.ProcessBoundsList(boundsList: string);
1481 var list: TStringList;
1482 i,j: integer;
1483 begin
1484 list := TStringList.Create;
1485 try
1486 list.Delimiter := ',';
1487 list.DelimitedText := boundsList;
1488 with FArrayData[FCurrentArray] do
1489 begin
1490 if dim <> list.Count then
1491 FSymbolStream.ShowError(sInvalidBoundsList,[boundsList]);
1492 SetLength(bounds,dim);
1493 for i := 0 to list.Count - 1 do
1494 begin
1495 j := Pos(':',list[i]);
1496 if j = 0 then
1497 raise Exception.CreateFmt(sInvalidBoundsList,[boundsList]);
1498 bounds[i].LowerBound := StrToInt(system.copy(list[i],1,j-1));
1499 bounds[i].UpperBound := StrToInt(system.copy(list[i],j+1,length(list[i])-j));
1500 end;
1501 end;
1502 finally
1503 list.Free;
1504 end;
1505 end;
1506
1507 constructor TIBXMLProcessor.Create;
1508 begin
1509 inherited Create;
1510 NextStatement;
1511 end;
1512
1513 destructor TIBXMLProcessor.Destroy;
1514 begin
1515 FreeMem(FBlobBuffer);
1516 inherited Destroy;
1517 end;
1518
1519 function TIBXMLProcessor.AnalyseXML(SymbolStream: TSymbolStream): string;
1520 var Symbol: TSQLSymbol;
1521 Done: boolean;
1522 XMLString: string;
1523 begin
1524 Result := '';
1525 XMLString := '';
1526 Done := false;
1527 FState := stInTag;
1528 FSymbolStream := SymbolStream;
1529 with SymbolStream do
1530 begin
1531 StartXMLTag(XMLTag);
1532 while not Done do
1533 with SymbolStream do
1534 begin
1535 Symbol := GetSymbol;
1536
1537 case Symbol of
1538 sqEOL:
1539 case FState of
1540 stQuotedAttributeValue,
1541 stTagged:
1542 XMLString += LineEnding;
1543 end;
1544
1545 ' ',sqTab:
1546 case FState of
1547 stQuotedAttributeValue,
1548 stTagged:
1549 XMLString += ' ';
1550 end;
1551
1552 ';':
1553 case FState of
1554 stQuotedAttributeValue,
1555 stTagged:
1556 XMLString += ';';
1557 else
1558 ShowError(sXMLError,[Symbol]);
1559 end;
1560
1561 '''':
1562 case FState of
1563 stQuotedAttributeValue,
1564 stTagged:
1565 XMLString += '''';
1566 else
1567 ShowError(sXMLError,[Symbol]);
1568 end;
1569
1570 '*':
1571 case FState of
1572 stQuotedAttributeValue,
1573 stTagged:
1574 XMLString += '*';
1575 else
1576 ShowError(sXMLError,[Symbol]);
1577 end;
1578
1579 '/':
1580 case FState of
1581 stQuotedAttributeValue,
1582 stTagged:
1583 XMLString += '/';
1584 else
1585 ShowError(sXMLError,[Symbol]);
1586 end;
1587
1588 '>':
1589 case FState of
1590 stEndTag:
1591 case XMLTag of
1592 xtBlob:
1593 begin
1594 Result := ':' + Format(ibx_blob+'%d',[FCurrentBlob]);
1595 Done := true;
1596 end;
1597 xtArray:
1598 begin
1599 Result := ':' + Format(ibx_array+'%d',[FCurrentArray]);
1600 Done := true;
1601 end;
1602 else
1603 FState := stTagged;
1604 end;
1605
1606 stInTag:
1607 begin
1608 XMLString := '';
1609 FState := stTagged;
1610 EnterTag;
1611 end;
1612
1613 stQuotedAttributeValue,
1614 stTagged:
1615 XMLString += '>';
1616
1617 else
1618 ShowError(sXMLError,[Symbol]);
1619 end;
1620
1621 sqTag:
1622 if FState = stTagged then
1623 begin
1624 FState := stInTag;
1625 StartXMLTag(XMLTag)
1626 end
1627 else
1628 ShowError(sXMLError,[Symbol]);
1629
1630 sqEndTag:
1631 if FState = stTagged then
1632 begin
1633 ProcessTagValue(XMLString);
1634 EndXMLTag(XMLTag);
1635 FState := stEndTag;
1636 end
1637 else
1638 ShowError(sXMLError,[Symbol]);
1639
1640 '=':
1641 case FState of
1642 stAttribute:
1643 FState := stAttributeValue;
1644
1645 stQuotedAttributeValue,
1646 stTagged:
1647 XMLString += '=';
1648
1649 else
1650 ShowError(sXMLError,[Symbol]);
1651 end;
1652
1653 '"':
1654 case FState of
1655 stAttributeValue:
1656 begin
1657 XMLString := '';
1658 FState := stQuotedAttributeValue;
1659 end;
1660
1661 stQuotedAttributeValue:
1662 begin
1663 ProcessAttributeValue(XMLString);
1664 FState := stInTag;
1665 end;
1666
1667 stTagged:
1668 XMLString += '"';
1669
1670 else
1671 ShowError(sXMLError,[Symbol]);
1672 end;
1673
1674 sqString:
1675 case FState of
1676 stInTag: {attribute name}
1677 begin
1678 FAttributeName := SymbolValue;
1679 FState := stAttribute;
1680 end;
1681
1682 stAttributeValue:
1683 begin
1684 ProcessAttributeValue(FString);
1685 FState := stInTag;
1686 end;
1687
1688 stQuotedAttributeValue,
1689 stTagged:
1690 XMLString += SymbolValue;
1691
1692 else
1693 ShowError(sXMLError,[Symbol]);
1694 end;
1695 else
1696 ShowError(sXMLError,[Symbol]);
1697 end
1698 end;
1699 end;
1700 end;
1701
1702 procedure TIBXMLProcessor.NextStatement;
1703 begin
1704 FXMLTagIndex := 0;
1705 SetLength(FBlobData,0);
1706 FCurrentBlob := -1;
1707 SetLength(FArrayData,0);
1708 FCurrentArray := -1;
1709 end;
1710
1711 class function TIBXMLProcessor.FormatBlob(Field: ISQLData): string;
1712 var TextOut: TStrings;
1713 begin
1714 TextOut := TStringList.Create;
1715 try
1716 TextOut.Add(Format('<blob subtype="%d">',[Field.getSubtype]));
1717 StringToHex(Field.AsString,TextOut,BlobLineLength);
1718 TextOut.Add('</blob>');
1719 Result := TextOut.Text;
1720 finally
1721 TextOut.Free;
1722 end;
1723 end;
1724
1725 class function TIBXMLProcessor.FormatArray(Database: TIBDatabase; ar: IArray
1726 ): string;
1727 var index: array of integer;
1728 TextOut: TStrings;
1729
1730 procedure AddElements(dim: integer; indent:string = ' ');
1731 var i: integer;
1732 recurse: boolean;
1733 begin
1734 SetLength(index,dim+1);
1735 recurse := dim < ar.GetDimensions - 1;
1736 with ar.GetBounds[dim] do
1737 for i := LowerBound to UpperBound do
1738 begin
1739 index[dim] := i;
1740 if recurse then
1741 begin
1742 TextOut.Add(Format('%s<elt id="%d">',[indent,i]));
1743 AddElements(dim+1,indent + ' ');
1744 TextOut.Add('</elt>');
1745 end
1746 else
1747 if ((ar.GetSQLType = SQL_TEXT) or (ar.GetSQLType = SQL_VARYING)) and
1748 (ar.GetCharSetID = 1) then
1749 TextOut.Add(Format('%s<elt ix="%d">%s</elt>',[indent,i,StringToHex(ar.GetAsString(index))]))
1750 else
1751 TextOut.Add(Format('%s<elt ix="%d">%s</elt>',[indent,i,ar.GetAsString(index)]));
1752 end;
1753 end;
1754
1755 var
1756 s: string;
1757 bounds: TArrayBounds;
1758 i: integer;
1759 boundsList: string;
1760 begin
1761 TextOut := TStringList.Create;
1762 try
1763 s := Format('<array dim = "%d" sqltype = "%d" length = "%d" relation_name = "%s" column_name = "%s"',
1764 [ar.GetDimensions,ar.GetSQLType,ar.GetSize,
1765 ar.GetTableName,ar.GetColumnName]);
1766 case ar.GetSQLType of
1767 SQL_DOUBLE, SQL_FLOAT, SQL_LONG, SQL_SHORT, SQL_D_FLOAT, SQL_INT64:
1768 s += Format(' scale = "%d"',[ ar.GetScale]);
1769 SQL_TEXT,
1770 SQL_VARYING:
1771 s += Format(' charset = "%s"',[Database.Attachment.GetCharsetName(ar.GetCharSetID)]);
1772 end;
1773 bounds := ar.GetBounds;
1774 boundsList := '';
1775 for i := 0 to length(bounds) - 1 do
1776 begin
1777 if i <> 0 then boundsList += ',';
1778 boundsList += Format('%d:%d',[bounds[i].LowerBound,bounds[i].UpperBound]);
1779 end;
1780 s += Format(' bounds="%s"',[boundsList]);
1781 s += '>';
1782 TextOut.Add(s);
1783
1784 SetLength(index,0);
1785 AddElements(0);
1786 TextOut.Add('</array>');
1787 Result := TextOut.Text;
1788 finally
1789 TextOut.Free;
1790 end;
1791 end;
1792
1793 { TInteractiveSymbolStream }
1794
1795 function TInteractiveSymbolStream.GetErrorPrefix: string;
1796 begin
1797 Result := '';
1798 end;
1799
1800 function TInteractiveSymbolStream.GetNextLine(var Line: string): boolean;
1801 begin
1802 if FNextStatement then
1803 write(FPrompt)
1804 else
1805 write(FContinuePrompt);
1806 Result := not EOF;
1807 if Result then
1808 readln(Line);
1809 end;
1810
1811 constructor TInteractiveSymbolStream.Create(aPrompt: string; aContinue: string);
1812 begin
1813 inherited Create;
1814 FPrompt := aPrompt;
1815 FContinuePrompt := aContinue;
1816 end;
1817
1818 function TInteractiveSymbolStream.GetSymbol: TSQLSymbol;
1819 begin
1820 if Terminated then
1821 Result := sqEOF
1822 else
1823 Result := inherited GetSymbol;
1824 end;
1825
1826 { TBatchSymbolStream }
1827
1828 function TBatchSymbolStream.GetErrorPrefix: string;
1829 begin
1830 Result := Format(sOnLineError,[FLineIndex,FIndex]);
1831 end;
1832
1833 function TBatchSymbolStream.GetNextLine(var Line: string): boolean;
1834 begin
1835 Result := FLineIndex < FLines.Count;
1836 if Result then
1837 begin
1838 Line := FLines[FLineIndex];
1839 // writeln('Next Line = ',Line);
1840 Inc(FLineIndex);
1841 if assigned(OnProgressEvent) then
1842 OnProgressEvent(self,false,1);
1843 end;
1844 end;
1845
1846 constructor TBatchSymbolStream.Create;
1847 begin
1848 inherited Create;
1849 FLines := TStringList.Create;
1850 end;
1851
1852 destructor TBatchSymbolStream.Destroy;
1853 begin
1854 if assigned(FLines) then FLines.Free;
1855 inherited Destroy;
1856 end;
1857
1858 procedure TBatchSymbolStream.SetStreamSource(Lines: TStrings);
1859 begin
1860 FLineIndex := 0;
1861 FLines.Assign(Lines);
1862 if assigned(OnProgressEvent) then
1863 OnProgressEvent(self,true,FLines.Count);
1864 end;
1865
1866 procedure TBatchSymbolStream.SetStreamSource(S: TStream);
1867 begin
1868 FLineIndex := 0;
1869 FLines.LoadFromStream(S);
1870 if assigned(OnProgressEvent) then
1871 OnProgressEvent(self,true,FLines.Count);
1872 end;
1873
1874 procedure TBatchSymbolStream.SetStreamSource(FileName: string);
1875 begin
1876 FLineIndex := 0;
1877 FLines.LoadFromFile(FileName);
1878 if assigned(OnProgressEvent) then
1879 OnProgressEvent(self,true,FLines.Count);
1880 end;
1881
1882 { TSymbolStream }
1883
1884 function TSymbolStream.GetNextSymbol(C: char): TSQLSymbol;
1885 begin
1886 Result := sqNone;
1887 if C = FTerminator then
1888 Result := sqTerminator
1889 else
1890 case C of
1891 #0..#8,#10..#31,' ':
1892 Result := ' ';
1893
1894 #9,';','"','''','/',
1895 '*','=','>','<',',':
1896 Result := C;
1897 else
1898 begin
1899 Result := sqString;
1900 FLastChar := C
1901 end
1902 end;
1903 end;
1904
1905 function TSymbolStream.FindTag(tag: string; var xmlTag: TXMLTag): boolean;
1906 var i: integer;
1907 begin
1908 Result := false;
1909 for i := 0 to Length(XMLTagDefs) - 1 do
1910 if XMLTagDefs[i].TagValue = tag then
1911 begin
1912 xmlTag := XMLTagDefs[i].XMLTag;
1913 Result := true;
1914 break;
1915 end;
1916 end;
1917
1918 constructor TSymbolStream.Create;
1919 begin
1920 inherited;
1921 FTerminator := ';';
1922 NextStatement;
1923 end;
1924
1925 procedure TSymbolStream.ShowError(msg: string; params: array of const);
1926 begin
1927 raise EIBClientError.CreateFmt(GetErrorPrefix + msg,params);
1928 end;
1929
1930 function TSymbolStream.GetSymbol: TSQLSymbol;
1931 var
1932 DelimitedText: string;
1933 CurState: (gsNone,gsInComment,gsInSingleQuotes,gsInDoubleQuotes);
1934 begin
1935 Result := sqNone;
1936 CurState := gsNone;
1937 DelimitedText := '';
1938 if FNextSymbol <> sqNone then
1939 begin
1940 Result := FNextSymbol;
1941 if Result = sqString then
1942 FString := FLastChar
1943 else
1944 FString := '';
1945 FNextSymbol := sqNone
1946 end;
1947
1948 while FNextSymbol = sqNone do {find the next symbol}
1949 begin
1950 if FIndex > Length(FLine) then
1951 begin
1952 FNextSymbol := sqEOL;
1953 FIndex := 0;
1954 end
1955 else
1956 begin
1957 if FIndex = 0 then
1958 begin
1959 if not GetNextLine(FLine) then
1960 begin
1961 Result := sqEOF;
1962 FNextSymbol := sqNone;
1963 Exit;
1964 end;
1965 FIndex := 1;
1966 FNextStatement := false;
1967 if assigned(OnNextLine) then
1968 OnNextLine(self,FLine);
1969 if CurState <> gsNone then
1970 DelimitedText += LineEnding;
1971 if Length(FLine) = 0 then
1972 continue;
1973 end;
1974 if CurState <> gsNone then
1975 DelimitedText += FLine[FIndex];
1976 FNextSymbol := GetNextSymbol(FLine[FIndex]);
1977 Inc(FIndex);
1978 end;
1979
1980 case CurState of
1981 gsNone:
1982 begin
1983 {combine if possible}
1984 case Result of
1985 sqNone:
1986 begin
1987 Result := FNextSymbol;
1988 if FNextSymbol = sqString then
1989 FString := FLastChar;
1990 FNextSymbol := sqNone
1991 end;
1992
1993 '/':
1994 if FXMLMode > 0 then
1995 break
1996 else
1997 if FNextSymbol = '*' then
1998 begin
1999 CurState := gsInComment;
2000 DelimitedText := '/*';
2001 Result := sqNone;
2002 FNextSymbol := sqNone
2003 end
2004 else
2005 if FNextSymbol = '/' then
2006 begin
2007 FString := '/*' + system.copy(FLine,FIndex,length(FLine)- FIndex + 1) + ' */';
2008 Result := sqCommentLine;
2009 FIndex := 0;
2010 FNextSymbol := sqNone
2011 end;
2012
2013 '<':
2014 if (FXMLMode > 0) and (FNextSymbol = '/') then
2015 begin
2016 Result := sqEndTag;
2017 FString := '';
2018 FNextSymbol := sqNone
2019 end
2020 else
2021 if FNextSymbol = sqString then
2022 begin
2023 Result := sqTag;
2024 FString := FLastChar;
2025 FNextSymbol := sqNone
2026 end;
2027
2028 '''':
2029 if FXMLMode > 0 then
2030 break
2031 else
2032 if FNextSymbol = '''' then
2033 begin
2034 Result := sqQuotedString;
2035 FString := '''''';
2036 FNextSymbol := sqNone
2037 end
2038 else
2039 begin
2040 CurState := gsInSingleQuotes;
2041 DelimitedText := '''';
2042 if FNextSymbol = sqEOL then
2043 DelimitedText += LineEnding
2044 else
2045 DelimitedText += FLine[FIndex-1];
2046 Result := sqNone;
2047 FNextSymbol := sqNone
2048 end;
2049
2050 '"':
2051 if FXMLMode > 0 then
2052 break
2053 else
2054 begin
2055 CurState := gsInDoubleQuotes;
2056 DelimitedText := '"';
2057 if FNextSymbol = sqEOL then
2058 DelimitedText += LineEnding
2059 else
2060 DelimitedText += FLine[FIndex-1];
2061 Result := sqNone;
2062 FNextSymbol := sqNone
2063 end;
2064
2065 sqTag,
2066 sqEndTag,
2067 sqString:
2068 if FNextSymbol = sqString then
2069 begin
2070 FString := FString + FLastChar;
2071 FNextSymbol := sqNone
2072 end;
2073 end
2074 end;
2075
2076 {Check for state exit condition}
2077 gsInSingleQuotes:
2078 if Result = '''' then
2079 begin
2080 CurState := gsNone;
2081 if FNextSymbol = sqEOL then
2082 FString := DelimitedText
2083 else
2084 FString := system.copy(DelimitedText,1,Length(DelimitedText)-1);
2085 Result := sqQuotedString;
2086 end;
2087
2088 gsInDoubleQuotes:
2089 if Result = '"' then
2090 begin
2091 CurState := gsNone;
2092 if FNextSymbol = sqEOL then
2093 FString := DelimitedText
2094 else
2095 FString := system.copy(DelimitedText,1,Length(DelimitedText)-1);
2096 Result := sqDoubleQuotedString;
2097 end;
2098
2099 gsInComment:
2100 if (Result = '*') and (FNextSymbol = '/') then
2101 begin
2102 CurState := gsNone;
2103 FString := DelimitedText;
2104 Result := sqComment;
2105 FNextSymbol := sqNone
2106 end;
2107
2108 end;
2109
2110 if (CurState <> gsNone) and (FNextSymbol <> sqNone) then
2111 begin
2112 Result := FNextSymbol;
2113 FNextSymbol := sqNone;
2114 end;
2115 end;
2116
2117 if (Result = sqTag) and (FNextSymbol <> sqNone) then
2118 begin
2119 if FindTag(FString,FXMLTag) then
2120 Inc(FXMLMode)
2121 else
2122 Result := sqString;
2123 end
2124 else
2125 if (Result = sqEndTag) and (FNextSymbol <> sqNone) then
2126 begin
2127 if FindTag(FString,FXMLTag) then
2128 Dec(FXMLMode)
2129 else
2130 Result := sqString;
2131 end;
2132
2133 if (FXMLMode = 0) and (Result = sqString) and (FString <> '') then
2134 begin
2135 if CompareText(FString,'begin') = 0 then
2136 Result := sqBegin
2137 else
2138 if CompareText(FString,'end') = 0 then
2139 Result := sqEnd
2140 else
2141 if CompareText(FString,'declare') = 0 then
2142 Result := sqDeclare
2143 else
2144 if CompareText(FString,'case') = 0 then
2145 Result := sqCase
2146 end;
2147 // writeln(Result,',',FString);
2148 end;
2149
2150 procedure TSymbolStream.NextStatement;
2151 begin
2152 FXMLTag := xtNone;
2153 FNextStatement := true;
2154 end;
2155
2156 end.
2157