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