ViewVC Help
View File | Revision Log | Show Annotations | Download File | View Changeset | Root Listing
root/public/ibx/trunk/runtime/nongui/ibxscript.pas
Revision: 353
Committed: Sat Oct 23 14:11:37 2021 UTC (2 years, 6 months ago) by tony
Content type: text/x-pascal
File size: 56476 byte(s)
Log Message:
Fixes Merged

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 sqltEOL:
520 stmt += LineEnding;
521
522 else
523 begin
524 if (tokentext = Terminator) and (Nested = 0) then
525 begin
526 EndOfStatement := true;
527 State := stDefault;
528 end
529 else
530 stmt += TokenText;
531 end;
532 end;
533 end;
534
535 {ignore begin..end blocks for Terminator detection }
536
537 stInBlock:
538 begin
539 case token of
540 sqltBegin:
541 begin
542 Inc(Nested);
543 stmt += TokenText;
544 end;
545
546 sqltEnd:
547 begin
548 Dec(Nested);
549 stmt += TokenText;
550 if Nested = 0 then
551 begin
552 State := stDefault;
553 EndOfStatement := true;
554 end;
555 end;
556
557 sqltCase:
558 {case constructs can appear within select statement in nested blocks.
559 We need to match the case constructs END token in order to parse the
560 block correctly. This is a simple parser and the only objective is
561 to determine the correct end of block. We therefore do not check to
562 ensure that the next end properly matches the case. The CASE is thus
563 treated the same as BEGIN. The Firebird SQL Parser will flag any errors
564 due to mismatched CASE/BEGIN END}
565 begin
566 Inc(Nested);
567 stmt += TokenText;
568 end;
569
570 sqltComment:
571 stmt += '/*' + TokenText + '*/';
572
573 sqltCommentLine:
574 stmt += '/* ' + TokenText + ' */' + LineEnding;
575
576 sqltQuotedString:
577 stmt += '''' + SQLSafeString(TokenText) + '''';
578
579 sqltIdentifierInDoubleQuotes:
580 stmt += '"' + TokenText + '"';
581
582 sqltEOL:
583 stmt += LineEnding;
584
585 else
586 stmt += TokenText;
587 end;
588 end;
589
590 {ignore array dimensions for Terminator detection }
591
592 stInArrayDim:
593 begin
594 case token of
595
596 sqltComment:
597 stmt += '/*' + TokenText + '*/';
598
599 sqltCommentLine:
600 stmt += '/* ' + TokenText + ' */' + LineEnding;
601
602 sqltCloseSquareBracket:
603 begin
604 stmt += TokenText;
605 State := stInStmt;
606 end;
607
608 sqltEOL:
609 stmt += LineEnding;
610
611 else
612 stmt += TokenText;
613 end;
614 end;
615
616 {ignore Declare statement for terminator - semi-colon terminates declaration}
617
618 stInDeclare:
619 begin
620 case token of
621
622 sqltComment:
623 stmt += '/*' + TokenText + '*/';
624
625 sqltCommentLine:
626 stmt += '/* ' + TokenText + ' */' + LineEnding;
627
628 sqltQuotedString:
629 stmt += '''' + SQLSafeString(TokenText) + ''''; {exists some DECLARE with cursor having SELECT ...\... rc.rdb$constraint_type = 'PRIMARY KEY');}
630
631 sqltSemiColon:
632 begin
633 State := stInStmt;
634 stmt += TokenText;
635 end;
636
637 sqltEOL:
638 stmt += LineEnding;
639
640 else
641 stmt += TokenText;
642 end;
643 end;
644 end;
645 end;
646 Result := stmt <> '';
647 end;
648
649 { TSQLXMLReader }
650
651 function TSQLXMLReader.FindTag(tag: string; var xmlTag: TXMLTag): boolean;
652 var i: TXMLTag;
653 begin
654 Result := false;
655 for i := xtBlob to xtElt do
656 if XMLTagDefs[i].TagValue = tag then
657 begin
658 xmlTag := XMLTagDefs[i].XMLTag;
659 Result := true;
660 break;
661 end;
662 end;
663
664 function TSQLXMLReader.GetArrayData(index: integer): TArrayData;
665 begin
666 if (index < 0) or (index > ArrayDataCount) then
667 ShowError(sArrayIndexError,[index]);
668 Result := FArrayData[index];
669 end;
670
671 function TSQLXMLReader.GetArrayDataCount: integer;
672 begin
673 Result := Length(FArrayData);
674 end;
675
676 function TSQLXMLReader.GetBlobData(index: integer): TBlobData;
677 begin
678 if (index < 0) or (index > BlobDataCount) then
679 ShowError(sBlobIndexError,[index]);
680 Result := FBlobData[index];
681 end;
682
683 function TSQLXMLReader.GetBlobDataCount: integer;
684 begin
685 Result := Length(FBlobData);
686 end;
687
688 function TSQLXMLReader.GetTagName(xmltag: TXMLTag): string;
689 var i: TXMLTag;
690 begin
691 Result := 'unknown';
692 for i := xtBlob to xtElt do
693 if XMLTagDefs[i].XMLTag = xmltag then
694 begin
695 Result := XMLTagDefs[i].TagValue;
696 Exit;
697 end;
698 end;
699
700 procedure TSQLXMLReader.ProcessAttributeValue(attrValue: string);
701 begin
702 case FXMLTagStack[FXMLTagIndex] of
703 xtBlob:
704 if FAttributeName = 'subtype' then
705 FBlobData[FCurrentBlob].SubType := StrToInt(attrValue)
706 else
707 ShowError(sXMLAttributeError,[FAttributeName,attrValue]);
708
709 xtArray:
710 if FAttributeName = 'sqltype' then
711 FArrayData[FCurrentArray].SQLType := StrToInt(attrValue)
712 else
713 if FAttributeName = 'relation_name' then
714 FArrayData[FCurrentArray].relationName := attrValue
715 else
716 if FAttributeName = 'column_name' then
717 FArrayData[FCurrentArray].columnName := attrValue
718 else
719 if FAttributeName = 'dim' then
720 FArrayData[FCurrentArray].Dim := StrToInt(attrValue)
721 else
722 if FAttributeName = 'length' then
723 FArrayData[FCurrentArray].Size := StrToInt(attrValue)
724 else
725 if FAttributeName = 'scale' then
726 FArrayData[FCurrentArray].Scale := StrToInt(attrValue)
727 else
728 if FAttributeName = 'charset' then
729 FArrayData[FCurrentArray].CharSet := attrValue
730 else
731 if FAttributeName = 'bounds' then
732 ProcessBoundsList(attrValue)
733 else
734 ShowError(sXMLAttributeError,[FAttributeName,attrValue]);
735
736 xtElt:
737 if FAttributeName = 'ix' then
738 with FArrayData[FCurrentArray] do
739 Index[CurrentRow] := StrToInt(attrValue)
740 else
741 ShowError(sXMLAttributeError,[FAttributeName,attrValue]);
742 end;
743 end;
744
745 procedure TSQLXMLReader.ProcessBoundsList(boundsList: string);
746 var list: TStringList;
747 i,j: integer;
748 begin
749 list := TStringList.Create;
750 try
751 list.Delimiter := ',';
752 list.DelimitedText := boundsList;
753 with FArrayData[FCurrentArray] do
754 begin
755 if dim <> list.Count then
756 ShowError(sInvalidBoundsList,[boundsList]);
757 SetLength(bounds,dim);
758 for i := 0 to list.Count - 1 do
759 begin
760 j := Pos(':',list[i]);
761 if j = 0 then
762 raise Exception.CreateFmt(sInvalidBoundsList,[boundsList]);
763 bounds[i].LowerBound := StrToInt(system.copy(list[i],1,j-1));
764 bounds[i].UpperBound := StrToInt(system.copy(list[i],j+1,length(list[i])-j));
765 end;
766 end;
767 finally
768 list.Free;
769 end;
770 end;
771
772 procedure TSQLXMLReader.ProcessTagValue(tagValue: string);
773
774 function nibble(hex: char): byte;
775 begin
776 case hex of
777 '0': Result := 0;
778 '1': Result := 1;
779 '2': Result := 2;
780 '3': Result := 3;
781 '4': Result := 4;
782 '5': Result := 5;
783 '6': Result := 6;
784 '7': Result := 7;
785 '8': Result := 8;
786 '9': Result := 9;
787 'a','A': Result := 10;
788 'b','B': Result := 11;
789 'c','C': Result := 12;
790 'd','D': Result := 13;
791 'e','E': Result := 14;
792 'f','F': Result := 15;
793 end;
794 end;
795
796 procedure RemoveWhiteSpace(var hexData: string);
797 var i: integer;
798 begin
799 {Remove White Space}
800 i := 1;
801 while i <= length(hexData) do
802 begin
803 case hexData[i] of
804 ' ',#9,#10,#13:
805 begin
806 if i < Length(hexData) then
807 Move(hexData[i+1],hexData[i],Length(hexData)-i);
808 SetLength(hexData,Length(hexData)-1);
809 end;
810 else
811 Inc(i);
812 end;
813 end;
814 end;
815
816 procedure WriteToBlob(hexData: string);
817 var i,j : integer;
818 blength: integer;
819 P: PChar;
820 begin
821 RemoveWhiteSpace(hexData);
822 if odd(length(hexData)) then
823 ShowError(sBinaryBlockMustbeEven,[nil]);
824 blength := Length(hexData) div 2;
825 IBAlloc(FBlobBuffer,0,blength);
826 j := 1;
827 P := FBlobBuffer;
828 for i := 1 to blength do
829 begin
830 P^ := char((nibble(hexData[j]) shl 4) or nibble(hexdata[j+1]));
831 Inc(j,2);
832 Inc(P);
833 end;
834 FBlobData[FCurrentBlob].BlobIntf.Write(FBlobBuffer^,blength);
835 end;
836
837 begin
838 if tagValue = '' then Exit;
839 case FXMLTagStack[FXMLTagIndex] of
840 xtBlob:
841 WriteToBlob(tagValue);
842
843 xtElt:
844 with FArrayData[FCurrentArray] do
845 ArrayIntf.SetAsString(index,tagValue);
846
847 end;
848 end;
849
850 procedure TSQLXMLReader.XMLTagInit(xmltag: TXMLTag);
851 begin
852 if FXMLTagIndex > MaxXMLTags then
853 ShowError(sXMLStackOverFlow,[nil]);
854 Inc(FXMLTagIndex);
855 FXMLTagStack[FXMLTagIndex] := xmltag;
856 FXMLString := '';
857
858 case xmltag of
859 xtBlob:
860 begin
861 Inc(FCurrentBlob);
862 SetLength(FBlobData,FCurrentBlob+1);
863 FBlobData[FCurrentBlob].BlobIntf := nil;
864 FBlobData[FCurrentBlob].SubType := 0;
865 end;
866
867 xtArray:
868 begin
869 Inc(FCurrentArray);
870 SetLength(FArrayData,FCurrentArray+1);
871 with FArrayData[FCurrentArray] do
872 begin
873 ArrayIntf := nil;
874 SQLType := 0;
875 dim := 0;
876 Size := 0;
877 Scale := 0;
878 CharSet := 'NONE';
879 SetLength(Index,0);
880 CurrentRow := -1;
881 end;
882 end;
883
884 xtElt:
885 with FArrayData[FCurrentArray] do
886 Inc(CurrentRow)
887 end;
888 end;
889
890 function TSQLXMLReader.XMLTagEnd(var xmltag: TXMLTag): boolean;
891 begin
892 if FXMLTagIndex = 0 then
893 ShowError(sXMLStackUnderflow,[nil]);
894
895 xmlTag := FXMLTagStack[FXMLTagIndex];
896 case FXMLTagStack[FXMLTagIndex] of
897 xtBlob:
898 FBlobData[FCurrentBlob].BlobIntf.Close;
899
900 xtArray:
901 FArrayData[FCurrentArray].ArrayIntf.SaveChanges;
902
903 xtElt:
904 Dec(FArrayData[FCurrentArray].CurrentRow);
905 end;
906 Dec(FXMLTagIndex);
907 Result := FXMLTagIndex = 0;
908 end;
909
910 procedure TSQLXMLReader.XMLTagEnter;
911 var aCharSetID: integer;
912 begin
913 if Database = nil then
914 ShowError(sNoDatabase);
915 if Transaction = nil then
916 ShowError(sNoTransaction);
917 case FXMLTagStack[FXMLTagIndex] of
918 xtBlob:
919 begin
920 Database.Connected := true;
921 Transaction.Active := true;
922 FBlobData[FCurrentBlob].BlobIntf := Database.Attachment.CreateBlob(
923 Transaction.TransactionIntf,FBlobData[FCurrentBlob].SubType);
924 end;
925
926 xtArray:
927 with FArrayData[FCurrentArray] do
928 begin
929 Database.Connected := true;
930 Transaction.Active := true;
931 Database.Attachment.CharSetName2CharSetID(CharSet,aCharSetID);
932 SetLength(Index,dim);
933 ArrayIntf := Database.Attachment.CreateArray(
934 Transaction.TransactionIntf,
935 Database.Attachment.CreateArrayMetaData(SQLType,
936 relationName,columnName,Scale,Size,
937 aCharSetID,dim,bounds)
938 );
939 end;
940 end;
941 end;
942
943 {This is where the XML tags are identified and the token stream modified in
944 consequence}
945
946 function TSQLXMLReader.TokenFound(var token: TSQLTokens): boolean;
947
948 procedure NotAnXMLTag;
949 begin
950 begin
951 if FXMLTagIndex = 0 then
952 {nothing to do with XML so go back to processing SQL}
953 begin
954 QueueToken(token);
955 ReleaseQueue(token);
956 FXMLState := stNoXML
957 end
958 else
959 begin
960 {Not an XML tag, so just push back to XML Data}
961 FXMLState := stXMLData;
962 FXMLString += GetQueuedText;
963 ResetQueue;
964 end;
965 end;
966 end;
967
968 var XMLTag: TXMLTag;
969 begin
970 Result := inherited TokenFound(token);
971 if not Result then Exit;
972
973 case FXMLState of
974 stNoXML:
975 if token = sqltLT then
976 begin
977 ResetQueue;
978 QueueToken(token); {save in case this is not XML}
979 FXMLState := stInTag;
980 end;
981
982 stInTag:
983 {Opening '<' found, now looking for tag name or end tag marker}
984 case token of
985 sqltIdentifier:
986 begin
987 if FindTag(TokenText,XMLTag) then
988 begin
989 XMLTagInit(XMLTag);
990 QueueToken(token);
991 FXMLState := stInTagBody;
992 end
993 else
994 NotAnXMLTag;
995 end;
996
997 sqltForwardSlash:
998 FXMLState := stInEndTag;
999
1000 else
1001 NotAnXMLTag;
1002 end {case token};
1003
1004 stInTagBody:
1005 {Tag name found. Now looking for attribute or closing '>'}
1006 case token of
1007 sqltIdentifier:
1008 begin
1009 FAttributeName := TokenText;
1010 QueueToken(token);
1011 FXMLState := stAttribute;
1012 end;
1013
1014 sqltGT:
1015 begin
1016 ResetQueue;
1017 XMLTagEnter;
1018 FXMLState := stXMLData;
1019 end;
1020
1021 sqltSpace,
1022 sqltEOL:
1023 QueueToken(token);
1024
1025 else
1026 NotAnXMLTag;
1027 end {case token};
1028
1029 stAttribute:
1030 {Attribute name found. Must be followed by an '=', a '>' or another tag name}
1031 case token of
1032 sqltEquals:
1033 begin
1034 QueueToken(token);
1035 FXMLState := stAttributeValue;
1036 end;
1037
1038 sqltSpace,
1039 sqltEOL:
1040 QueueToken(token);
1041
1042 sqltIdentifier:
1043 begin
1044 ProcessAttributeValue('');
1045 FAttributeName := TokenText;
1046 QueueToken(token);
1047 FXMLState := stAttribute;
1048 end;
1049
1050 sqltGT:
1051 begin
1052 ProcessAttributeValue('');
1053 ResetQueue;
1054 XMLTagEnter;
1055 FXMLState := stXMLData;
1056 end;
1057
1058 else
1059 NotAnXMLTag;
1060 end; {case token}
1061
1062 stAttributeValue:
1063 {Looking for attribute value as a single identifier or a double quoted value}
1064 case token of
1065 sqltIdentifier,sqltIdentifierInDoubleQuotes:
1066 begin
1067 ProcessAttributeValue(TokenText);
1068 QueueToken(token);
1069 FXMLState := stInTagBody;
1070 end;
1071
1072 sqltSpace,
1073 sqltEOL:
1074 QueueToken(token);
1075
1076 else
1077 NotAnXMLTag;
1078 end; {case token}
1079
1080 stXMLData:
1081 if token = sqltLT then
1082 begin
1083 QueueToken(token); {save in case this is not XML}
1084 FXMLState := stInTag;
1085 end
1086 else
1087 FXMLString += TokenText;
1088
1089 stInEndTag:
1090 {Opening '</' found, now looking for tag name}
1091 case token of
1092 sqltIdentifier:
1093 begin
1094 if FindTag(TokenText,XMLTag) and (XMLTag = FXMLTagStack[FXMLTagIndex]) then
1095 begin
1096 QueueToken(token);
1097 FXMLState := stInEndTagBody;
1098 end
1099 else
1100 ShowError(sInvalidEndTag,[TokenText]);
1101 end;
1102 else
1103 NotAnXMLTag;
1104 end {case token};
1105
1106 stInEndTagBody:
1107 {End tag name found, now looping for closing '>'}
1108 case Token of
1109 sqltGT:
1110 begin
1111 ProcessTagValue(FXMLString);
1112 if XMLTagEnd(XMLTag) then
1113 begin
1114 ResetQueue;
1115 QueueToken(sqltColon,':');
1116 case XMLTag of
1117 xtBlob:
1118 QueueToken(sqltIdentifier,Format(ibx_blob+'%d',[FCurrentBlob]));
1119
1120 xtArray:
1121 QueueToken(sqltIdentifier, Format(ibx_array+'%d',[FCurrentArray]));
1122 end;
1123 ReleaseQueue(token);
1124 FXMLState := stNoXML;
1125 end
1126 else
1127 FXMLState := stXMLData;
1128 end;
1129
1130 sqltSpace,
1131 sqltEOL:
1132 QueueToken(token);
1133
1134 else
1135 ShowError(sBadEndTagClosing);
1136 end; {case token}
1137
1138 end {Case FState};
1139
1140 {Only allow token to be returned if not processing an XML tag}
1141
1142 Result := FXMLState = stNoXML;
1143 end;
1144
1145 procedure TSQLXMLReader.ShowError(msg: string; params: array of const);
1146 begin
1147 raise EIBClientError.CreateFmt(GetErrorPrefix + msg,params);
1148 end;
1149
1150 procedure TSQLXMLReader.ShowError(msg: string);
1151 begin
1152 ShowError(msg,[nil]);
1153 end;
1154
1155 constructor TSQLXMLReader.Create;
1156 begin
1157 inherited;
1158 FXMLState := stNoXML;
1159 end;
1160
1161 procedure TSQLXMLReader.FreeDataObjects;
1162 begin
1163 FXMLTagIndex := 0;
1164 SetLength(FBlobData,0);
1165 FCurrentBlob := -1;
1166 SetLength(FArrayData,0);
1167 FCurrentArray := -1;
1168 end;
1169
1170 class function TSQLXMLReader.FormatBlob(Field: ISQLData): string;
1171 var TextOut: TStrings;
1172 begin
1173 TextOut := TStringList.Create;
1174 try
1175 TextOut.Add(Format('<blob subtype="%d">',[Field.getSubtype]));
1176 StringToHex(Field.AsString,TextOut,BlobLineLength);
1177 TextOut.Add('</blob>');
1178 Result := TextOut.Text;
1179 finally
1180 TextOut.Free;
1181 end;
1182 end;
1183
1184 class function TSQLXMLReader.FormatArray(Database: TIBDatabase; ar: IArray
1185 ): string;
1186 var index: array of integer;
1187 TextOut: TStrings;
1188
1189 procedure AddElements(dim: integer; indent:string = ' ');
1190 var i: integer;
1191 recurse: boolean;
1192 begin
1193 SetLength(index,dim+1);
1194 recurse := dim < ar.GetDimensions - 1;
1195 with ar.GetBounds[dim] do
1196 for i := LowerBound to UpperBound do
1197 begin
1198 index[dim] := i;
1199 if recurse then
1200 begin
1201 TextOut.Add(Format('%s<elt id="%d">',[indent,i]));
1202 AddElements(dim+1,indent + ' ');
1203 TextOut.Add('</elt>');
1204 end
1205 else
1206 if ((ar.GetSQLType = SQL_TEXT) or (ar.GetSQLType = SQL_VARYING)) and
1207 (ar.GetCharSetID = 1) then
1208 TextOut.Add(Format('%s<elt ix="%d">%s</elt>',[indent,i,StringToHex(ar.GetAsString(index))]))
1209 else
1210 TextOut.Add(Format('%s<elt ix="%d">%s</elt>',[indent,i,ar.GetAsString(index)]));
1211 end;
1212 end;
1213
1214 var
1215 s: string;
1216 bounds: TArrayBounds;
1217 i: integer;
1218 boundsList: string;
1219 begin
1220 TextOut := TStringList.Create;
1221 try
1222 if ar.GetCharSetWidth = 0 then
1223 s := Format('<array dim = "%d" sqltype = "%d" length = "%d" relation_name = "%s" column_name = "%s"',
1224 [ar.GetDimensions,ar.GetSQLType,ar.GetSize,
1225 ar.GetTableName,ar.GetColumnName])
1226 else
1227 s := Format('<array dim = "%d" sqltype = "%d" length = "%d" relation_name = "%s" column_name = "%s"',
1228 [ar.GetDimensions,ar.GetSQLType,ar.GetSize div ar.GetCharSetWidth,
1229 ar.GetTableName,ar.GetColumnName]);
1230 case ar.GetSQLType of
1231 SQL_DOUBLE, SQL_FLOAT, SQL_LONG, SQL_SHORT, SQL_D_FLOAT, SQL_INT64:
1232 s += Format(' scale = "%d"',[ ar.GetScale]);
1233 SQL_TEXT,
1234 SQL_VARYING:
1235 s += Format(' charset = "%s"',[Database.Attachment.GetCharsetName(ar.GetCharSetID)]);
1236 end;
1237 bounds := ar.GetBounds;
1238 boundsList := '';
1239 for i := 0 to length(bounds) - 1 do
1240 begin
1241 if i <> 0 then boundsList += ',';
1242 boundsList += Format('%d:%d',[bounds[i].LowerBound,bounds[i].UpperBound]);
1243 end;
1244 s += Format(' bounds="%s"',[boundsList]);
1245 s += '>';
1246 TextOut.Add(s);
1247
1248 SetLength(index,0);
1249 AddElements(0);
1250 TextOut.Add('</array>');
1251 Result := TextOut.Text;
1252 finally
1253 TextOut.Free;
1254 end; end;
1255
1256 procedure TSQLXMLReader.Reset;
1257 begin
1258 inherited Reset;
1259 FreeDataObjects;
1260 FXMLString := '';
1261 FreeMem(FBlobBuffer);
1262 end;
1263
1264
1265
1266 { TIBXScript }
1267
1268 constructor TIBXScript.Create(aOwner: TComponent);
1269 begin
1270 inherited Create(aOwner);
1271 SetSQLStatementReader(TBatchSQLStatementReader.Create);
1272 end;
1273
1274 function TIBXScript.PerformUpdate(SQLFile: string; aAutoDDL: boolean): boolean;
1275 begin
1276 FAutoDDL := aAutoDDL;
1277 Result := RunScript( SQLFile);
1278 end;
1279
1280 function TIBXScript.PerformUpdate(SQLStream: TStream; aAutoDDL: boolean
1281 ): boolean;
1282 begin
1283 FAutoDDL := aAutoDDL;
1284 Result := RunScript(SQLStream);
1285 end;
1286
1287 function TIBXScript.RunScript(SQLFile: string): boolean;
1288 begin
1289 TBatchSQLStatementReader(FSQLReader).SetStreamSource(SQLFile);
1290 Result := ProcessStream;
1291 end;
1292
1293 function TIBXScript.RunScript(SQLStream: TStream): boolean;
1294 begin
1295 TBatchSQLStatementReader(FSQLReader).SetStreamSource(SQLStream);
1296 Result := ProcessStream;
1297 end;
1298
1299 function TIBXScript.RunScript(SQLLines: TStrings): boolean;
1300 begin
1301 TBatchSQLStatementReader(FSQLReader).SetStreamSource(SQLLines);
1302 Result := ProcessStream;
1303 end;
1304
1305 function TIBXScript.ExecSQLScript(sql: string): boolean;
1306 begin
1307 TBatchSQLStatementReader(FSQLReader).SetStringStreamSource(sql);
1308 Result := ProcessStream;
1309 end;
1310
1311 { TCustomIBXScript }
1312
1313 procedure TCustomIBXScript.Add2Log(const Msg: string; IsError: boolean);
1314 begin
1315 if IsError then
1316 begin
1317 if assigned(OnErrorLog) then OnErrorLog(self,Msg)
1318 end
1319 else
1320 if assigned(FOnOutputLog) then FOnOutputLog(self,Msg)
1321 end;
1322
1323 procedure TCustomIBXScript.DoCommit;
1324 begin
1325 with GetTransaction do
1326 if InTransaction then Commit;
1327 end;
1328
1329 procedure TCustomIBXScript.DoReconnect;
1330 begin
1331 with GetTransaction do
1332 if InTransaction then Commit;
1333 Database.Reconnect;
1334 end;
1335
1336 procedure TCustomIBXScript.ExecSQL(stmt: string);
1337 var DDL: boolean;
1338 I: integer;
1339 begin
1340 Database.Connected := true;
1341 FISQL.SQL.Text := stmt;
1342 FISQL.Transaction := GetTransaction;
1343 FISQL.Transaction.Active := true;
1344 // FISQL.ParamCheck := not FSQLReader.HasBegin; {Probably PSQL}
1345 FISQL.Prepare;
1346 FISQL.Statement.EnableStatistics(ShowPerformanceStats);
1347
1348 if FISQL.SQLStatementType in [SQLInsert, SQLUpdate, SQLDelete] then
1349 begin
1350 {Interpret parameters}
1351 for I := 0 to FISQL.Params.Count - 1 do
1352 SetParamValue(FISQL.Params[I]);
1353 end;
1354
1355 if FISQL.SQLStatementType = SQLSelect then
1356 begin
1357 if assigned(OnSelectSQL) then
1358 OnSelectSQL(self,stmt)
1359 else
1360 DefaultSelectSQLHandler(stmt);
1361 end
1362 else
1363 begin
1364 DDL := FISQL.SQLStatementType = SQLDDL;
1365 if not DDL or not FIgnoreGrants or (Pos('GRANT',AnsiUpperCase(Trim(stmt))) <> 1) then
1366 begin
1367 FISQL.ExecQuery;
1368 if ShowAffectedRows and not DDL then
1369 Add2Log('Rows Affected: ' + IntToStr(FISQL.RowsAffected));
1370 if not DDL then
1371 TIBCustomDataOutput.ShowPerfStats(FISQL.Statement,@Add2Log);
1372 end;
1373
1374 if FAutoDDL and DDL then
1375 FISQL.Transaction.Commit;
1376 FISQL.Close;
1377 end;
1378 FISQL.SQL.Clear;
1379 end;
1380
1381 function TCustomIBXScript.GetOnProgressEvent: TOnProgressEvent;
1382 begin
1383 Result := FSQLReader.OnProgressEvent;
1384 end;
1385
1386 function TCustomIBXScript.GetTransaction: TIBTransaction;
1387 begin
1388 if not (csDesigning in ComponentState) and (FTransaction = nil) then
1389 Result := FInternalTransaction
1390 else
1391 Result := FTransaction;
1392 end;
1393
1394 procedure TCustomIBXScript.EchoNextLine(Sender: TObject; Line: string);
1395 begin
1396 if Echo then Add2Log(Line);
1397 end;
1398
1399 procedure TCustomIBXScript.Notification(AComponent: TComponent;
1400 Operation: TOperation);
1401 begin
1402 inherited Notification(AComponent, Operation);
1403 if (AComponent = FDatabase) and (Operation = opRemove) then
1404 FDatabase := nil;
1405 if (AComponent = FTransaction) and (Operation = opRemove) then
1406 FTransaction := nil;
1407 if (AComponent = DataOutputFormatter) and (Operation = opRemove) then
1408 FDataOutputFormatter := nil;
1409 end;
1410
1411 procedure TCustomIBXScript.SetDatabase(AValue: TIBDatabase);
1412 begin
1413 if not (csLoading in ComponentState) and (FDatabase = AValue) then Exit;
1414 FDatabase := AValue;
1415 FISQL.Database := AValue;
1416 FSQLReader.Database := AValue;
1417 FInternalTransaction.Active := false;
1418 FInternalTransaction.DefaultDatabase := AValue;
1419 end;
1420
1421 procedure TCustomIBXScript.SetDataOutputFormatter(AValue: TIBCustomDataOutput);
1422 begin
1423 if FDataOutputFormatter = AValue then Exit;
1424 if (FDataOutputFormatter <> nil) and (AValue <> nil) then
1425 AValue.Assign(FDataOutputFormatter);
1426 FDataOutputFormatter := AValue;
1427 if FDataOutputFormatter <> nil then
1428 FDataOutputFormatter.Database := Database;
1429 end;
1430
1431 procedure TCustomIBXScript.SetOnProgressEvent(AValue: TOnProgressEvent);
1432 begin
1433 FSQLReader.OnProgressEvent := AValue;
1434 end;
1435
1436 procedure TCustomIBXScript.SetParamValue(SQLVar: ISQLParam);
1437 var BlobID: TISC_QUAD;
1438 ix: integer;
1439 begin
1440 if (SQLVar.SQLType = SQL_BLOB) and (Pos(TSQLXMLReader.ibx_blob,SQLVar.Name) = 1) then
1441 begin
1442 ix := StrToInt(system.copy(SQLVar.Name,length(TSQLXMLReader.ibx_blob)+1,length(SQLVar.Name)-length(TSQLXMLReader.ibx_blob)));
1443 SQLVar.AsBlob := FSQLReader.BlobData[ix].BlobIntf;
1444 Exit;
1445 end
1446 else
1447 if (SQLVar.SQLType = SQL_ARRAY) and (Pos(TSQLXMLReader.ibx_array,SQLVar.Name) = 1) then
1448 begin
1449 ix := StrToInt(system.copy(SQLVar.Name,length(TSQLXMLReader.ibx_array)+1,length(SQLVar.Name)-length(TSQLXMLReader.ibx_array)));
1450 SQLVar.AsArray := FSQLReader.ArrayData[ix].ArrayIntf;
1451 Exit;
1452 end;
1453
1454 if assigned(FGetParamValue) and (SQLVar.SQLType = SQL_BLOB) then
1455 begin
1456 Add2Log(Format(sResolveQueryParam,[SQLVar.Name]));
1457 GetParamValue(self,SQLVar.Name,BlobID);
1458 if (BlobID.gds_quad_high = 0) and (BlobID.gds_quad_low = 0) then
1459 SQLVar.Clear
1460 else
1461 SQLVar.AsQuad := BlobID
1462 end
1463 else
1464 raise Exception.Create(sNoParamQueries);
1465 end;
1466
1467 procedure TCustomIBXScript.SetShowPerformanceStats(AValue: boolean);
1468 begin
1469 if FShowPerformanceStats = AValue then Exit;
1470 FShowPerformanceStats := AValue;
1471 if assigned(DataOutputFormatter) then
1472 DataOutputFormatter.ShowPerformanceStats := AValue;
1473 end;
1474
1475 function TCustomIBXScript.ProcessStream: boolean;
1476 var stmt: string;
1477 begin
1478 Result := false;
1479 while FSQLReader.GetNextStatement(stmt) do
1480 try
1481 stmt := trim(stmt);
1482 // writeln('stmt = "',stmt,'"');
1483 if stmt = '' then continue;
1484 if not ProcessStatement(stmt) then
1485 ExecSQL(stmt);
1486
1487 except on E:Exception do
1488 begin
1489 with GetTransaction do
1490 if InTransaction then Rollback;
1491 FSQLReader.Terminator := TSQLStatementReader.DefaultTerminator;
1492 if assigned(OnErrorLog) then
1493 begin
1494 Add2Log(Format(sStatementError,[FSQLReader.GetErrorPrefix,
1495 E.Message,stmt]),true);
1496 if StopOnFirstError then Exit;
1497 end
1498 else
1499 raise;
1500 end
1501 end;
1502 Result := true;
1503 end;
1504
1505 procedure TCustomIBXScript.SetSQLStatementReader(
1506 SQLStatementReader: TSQLStatementReader);
1507 begin
1508 FSQLReader := SQLStatementReader;
1509 FSQLReader.OnNextLine := @EchoNextLine;
1510 FSQLReader.Transaction := FInternalTransaction;
1511 end;
1512
1513 function TCustomIBXScript.ProcessStatement(stmt: string): boolean;
1514 var command: string;
1515
1516 function Toggle(aValue: string): boolean;
1517 begin
1518 aValue := AnsiUpperCase(aValue);
1519 if aValue = 'ON' then
1520 Result := true
1521 else
1522 if aValue = 'OFF' then
1523 Result := false
1524 else
1525 raise Exception.CreateFmt(sInvalidSetStatement, [command,stmt]);
1526 end;
1527
1528 procedure ExtractUserInfo;
1529 var RegexObj: TRegExpr;
1530 begin
1531 RegexObj := TRegExpr.Create;
1532 try
1533 RegexObj.ModifierG := false; {turn off greedy matches}
1534 RegexObj.Expression := ' +USER +''(.+)''';
1535 if RegexObj.Exec(stmt) then
1536 FDatabase.Params.Values['user_name'] := RegexObj.Match[1];
1537
1538 RegexObj.Expression := ' +PASSWORD +''(.+)''';
1539 if RegexObj.Exec(stmt) then
1540 FDatabase.Params.Values['password'] :=
1541 system.copy(stmt,RegexObj.MatchPos[1],RegexObj.MatchLen[1]);
1542 finally
1543 RegexObj.Free;
1544 end;
1545 end;
1546
1547 procedure ExtractConnectInfo;
1548 var RegexObj: TRegExpr;
1549 begin
1550 ExtractUserInfo;
1551 RegexObj := TRegExpr.Create;
1552 try
1553 RegexObj.ModifierG := false; {turn off greedy matches}
1554 RegexObj.ModifierI := true; {case insensitive}
1555 RegexObj.Expression := '^ *CONNECT +''(.*)''';
1556 if RegexObj.Exec(stmt) then
1557 begin
1558 FDatabase.DatabaseName := system.copy(stmt,RegexObj.MatchPos[1],RegexObj.MatchLen[1]);
1559 end;
1560
1561 RegexObj.Expression := ' +ROLE +''(.+)''';
1562 if RegexObj.Exec(stmt) then
1563 FDatabase.Params.Values['sql_role_name'] := RegexObj.Match[1]
1564 else
1565 with FDatabase.Params do
1566 if IndexOfName('sql_role_name') <> -1 then
1567 Delete(IndexOfName('sql_role_name'));
1568
1569 RegexObj.Expression := ' +CACHE +([0-9]+)';
1570 if RegexObj.Exec(stmt) then
1571 FDatabase.Params.Values['cache_manager'] := RegexObj.Match[1]
1572 else
1573 with FDatabase.Params do
1574 if IndexOfName('cache_manager') <> -1 then
1575 Delete(IndexOfName('cache_manager'));
1576 finally
1577 RegexObj.Free;
1578 end;
1579 end;
1580
1581 procedure UpdateUserPassword;
1582 var RegexObj: TRegExpr;
1583 begin
1584 RegexObj := TRegExpr.Create;
1585 try
1586 RegexObj.ModifierG := false; {turn off greedy matches}
1587 RegexObj.ModifierI := true; {case insensitive}
1588 RegexObj.Expression := '^ *CREATE +(DATABASE|SCHEMA) +(''.*'') +USER +''(.+)''';
1589 if not RegexObj.Exec(stmt) and (FDatabase.Params.IndexOfName('user_name') <> -1) then
1590 begin
1591 RegexObj.Expression := '^ *CREATE +(DATABASE|SCHEMA) +(''.*'')';
1592 if RegexObj.Exec(stmt) then
1593 begin
1594 system.Insert(' USER ''' + FDatabase.Params.Values['user_name'] +'''',stmt,
1595 RegexObj.MatchPos[2] + RegexObj.MatchLen[2]);
1596 end;
1597 end;
1598
1599 RegexObj.Expression := '^ *CREATE +(DATABASE|SCHEMA) +''.*'' +USER +''.+'' PASSWORD +''(.+)''';
1600 if not RegexObj.Exec(stmt) and (FDatabase.Params.IndexOfName('password') <> -1) then
1601 begin
1602 RegexObj.Expression := '^ *CREATE +(DATABASE|SCHEMA) +''.*'' +(USER +''.+'')';
1603 if RegexObj.Exec(stmt) then
1604 begin
1605 system.Insert(' PASSWORD ''' + FDatabase.Params.Values['password'] +'''',stmt,
1606 RegexObj.MatchPos[2] + RegexObj.MatchLen[2]);
1607 end;
1608 end;
1609 finally
1610 RegexObj.Free;
1611 end;
1612 end;
1613
1614 var RegexObj: TRegExpr;
1615 n: integer;
1616 charsetid: integer;
1617 param: string;
1618 Terminator: char;
1619 FileName: string;
1620 DBConnected: boolean;
1621 LoginPrompt: boolean;
1622 begin
1623 Result := false;
1624 Terminator := FSQLReader.Terminator;
1625 RegexObj := TRegExpr.Create;
1626 try
1627 {process create database}
1628 RegexObj.ModifierI := true; {case insensitive}
1629 RegexObj.Expression := '^ *CREATE +(DATABASE|SCHEMA) +''(.*)''(.*)(\' + Terminator + '|)';
1630 if RegexObj.Exec(stmt) then
1631 begin
1632 if IgnoreCreateDatabase then
1633 begin
1634 Result := true;
1635 Exit;
1636 end;
1637 FileName := system.copy(stmt,RegexObj.MatchPos[2], RegexObj.MatchLen[2]);
1638 if assigned(FOnCreateDatabase) then
1639 OnCreateDatabase(self,FileName);
1640 stmt := 'CREATE DATABASE ''' + FileName + '''' + system.copy(stmt,RegexObj.MatchPos[3], RegexObj.MatchLen[3]);
1641 UpdateUserPassword;
1642 if FDatabase.Connected then
1643 FDatabase.Dropdatabase;
1644 FDatabase.CreateDatabase(stmt);
1645 Result := true;
1646 Exit;
1647 end;
1648
1649 {process connect statement}
1650 RegexObj.Expression := '^ *CONNECT +.*(\' + Terminator + '|)';
1651 if RegexObj.Exec(stmt) then
1652 begin
1653 ExtractConnectInfo;
1654 FDatabase.Connected := false;
1655 FDatabase.Connected := true;
1656 Result := true;
1657 Exit;
1658 end;
1659
1660 {Process Drop Database}
1661 RegexObj.Expression := '^ *DROP +DATABASE *(\' + Terminator + '|)';
1662 if RegexObj.Exec(stmt) then
1663 begin
1664 FDatabase.DropDatabase;
1665 Result := true;
1666 Exit;
1667 end;
1668
1669 {process commit statement}
1670 RegexObj.Expression := '^ *COMMIT *(\' + Terminator + '|)';
1671 if RegexObj.Exec(stmt) then
1672 begin
1673 DoCommit;
1674 Result := true;
1675 Exit;
1676 end;
1677
1678 {process Reconnect statement}
1679 RegexObj.Expression := '^ *RECONNECT *(\' + Terminator + '|)';
1680 if RegexObj.Exec(stmt) then
1681 begin
1682 DoReconnect;
1683 Result := true;
1684 Exit;
1685 end;
1686
1687
1688 {Process Set Term}
1689 RegexObj.Expression := '^ *SET +TERM +(.) *(\' + Terminator + '|)';
1690 if RegexObj.Exec(stmt) then
1691 begin
1692 FSQLReader.Terminator := RegexObj.Match[1][1];
1693 Result := true;
1694 Exit;
1695 end;
1696
1697 {process Set SQL Dialect}
1698 RegexObj.Expression := '^ *SET +SQL +DIALECT +([0-9]) *(\' + Terminator + '|)';
1699 if RegexObj.Exec(stmt) then
1700 begin
1701 n := StrToInt(RegexObj.Match[1]);
1702 if Database.SQLDialect <> n then
1703 begin
1704 Database.SQLDialect := n;
1705 if Database.Connected then
1706 DoReconnect;
1707 end;
1708 Result := true;
1709 Exit;
1710 end;
1711
1712 {Process Remaining Set statements}
1713 RegexObj.Expression := '^ *SET +([A-Z]+)( +[A-Z0-9]+|) *(\' + Terminator + '|)';
1714 if RegexObj.Exec(stmt) then
1715 begin
1716 command := AnsiUpperCase(RegexObj.Match[1]);
1717 param := trim(RegexObj.Match[2]);
1718 if command = 'GENERATOR' then
1719 begin
1720 Result := false;
1721 Exit;
1722 end;
1723 if command = 'AUTODDL' then
1724 AutoDDL := ((RegexObj.MatchLen[2] = 0) and not AutoDDL) or
1725 (RegexObj.MatchLen[2] > 0) and Toggle(param)
1726 else
1727 if command = 'BAIL' then
1728 StopOnFirstError := ((RegexObj.MatchLen[2] = 0) and not StopOnFirstError) or
1729 (RegexObj.MatchLen[2] > 0) and Toggle(param)
1730 else
1731 if command = 'ECHO' then
1732 Echo := ((RegexObj.MatchLen[2] = 0) and not Echo) or
1733 (RegexObj.MatchLen[2] > 0) and Toggle(param)
1734 else
1735 if command = 'COUNT' then
1736 ShowAffectedRows := ((RegexObj.MatchLen[2] = 0) and not ShowAffectedRows) or
1737 (RegexObj.MatchLen[2] > 0) and Toggle(param)
1738 else
1739 if command = 'STATS' then
1740 ShowPerformanceStats := ((RegexObj.MatchLen[2] = 0) and not FShowPerformanceStats) or
1741 (RegexObj.MatchLen[2] > 0) and Toggle(param)
1742 else
1743 if command = 'NAMES' then
1744 begin
1745 if Database.Attachment.CharSetName2CharSetID(param,charsetid) then
1746 begin
1747 DBConnected := Database.Connected;
1748 LoginPrompt := Database.LoginPrompt;
1749 Database.LoginPrompt := false;
1750 Database.Connected := false;
1751 Database.Params.Values['lc_ctype'] := param;
1752 Database.Connected := DBConnected;
1753 Database.LoginPrompt := LoginPrompt;
1754 end
1755 else
1756 raise Exception.CreateFmt(sInvalidCharacterSet, [param,stmt]);
1757 end
1758 else
1759 begin
1760 if assigned(DataOutputFormatter) then
1761 DataOutputFormatter.SetCommand(command,param,stmt,Result);
1762 if not Result then
1763 begin
1764 if assigned(OnSetStatement) then
1765 OnSetStatement(self,command,param,stmt,Result)
1766 else
1767 raise Exception.CreateFmt(sInvalidSetStatement, [command,stmt]);
1768 end;
1769 Exit;
1770 end;
1771 Result := true;
1772 Exit;
1773 end;
1774
1775 finally
1776 RegexObj.Free;
1777 end;
1778 end;
1779
1780 procedure TCustomIBXScript.SetTransaction(AValue: TIBTransaction);
1781 begin
1782 if FTransaction = AValue then Exit;
1783 FTransaction := AValue;
1784 if FTransaction = nil then
1785 FSQLReader.Transaction := FInternalTransaction
1786 else
1787 FSQLReader.Transaction := FTransaction;
1788 end;
1789
1790 constructor TCustomIBXScript.Create(aOwner: TComponent);
1791 begin
1792 inherited Create(aOwner);
1793 FStopOnFirstError := true;
1794 FEcho := true;
1795 FAutoDDL := true;
1796 FISQL := TIBSQL.Create(self);
1797 FISQL.ParamCheck := true;
1798 FInternalTransaction := TIBTransaction.Create(self);
1799 FInternalTransaction.Params.Clear;
1800 FInternalTransaction.Params.Add('concurrency');
1801 FInternalTransaction.Params.Add('wait');
1802 end;
1803
1804 destructor TCustomIBXScript.Destroy;
1805 begin
1806 if FSQLReader <> nil then FSQLReader.Free;
1807 if FISQL <> nil then FISQL.Free;
1808 if FInternalTransaction <> nil then FInternalTransaction.Free;
1809 inherited Destroy;
1810 end;
1811
1812 procedure TCustomIBXScript.DefaultSelectSQLHandler(aSQLText: string);
1813 begin
1814 if assigned(DataOutputFormatter) then
1815 DataOutputFormatter.DataOut(aSQLText,@Add2Log)
1816 else
1817 FSQLReader.ShowError(sNoSelectSQL);
1818 end;
1819
1820 { TInteractiveSQLStatementReader }
1821
1822 function TInteractiveSQLStatementReader.GetErrorPrefix: string;
1823 begin
1824 Result := '';
1825 end;
1826
1827 function TInteractiveSQLStatementReader.GetNextLine(var Line: string): boolean;
1828 begin
1829 if FNextStatement then
1830 write(FPrompt)
1831 else
1832 write(FContinuePrompt);
1833 Result := not system.EOF;
1834 if Result then
1835 begin
1836 readln(Line);
1837 EchoNextLine(Line);
1838 end;
1839 end;
1840
1841 function TInteractiveSQLStatementReader.GetChar: char;
1842 begin
1843 if Terminated then
1844 Result := #0
1845 else
1846 if FLineIndex > Length(FLine) then
1847 begin
1848 Result := LF;
1849 FLineIndex := 0;
1850 end
1851 else
1852 if FLineIndex = 0 then
1853 begin
1854 if not GetNextLine(FLine) then
1855 Result := #0
1856 else
1857 if Length(FLine) = 0 then
1858 Result := LF
1859 else
1860 begin
1861 Result := FLine[1];
1862 FLineIndex := 2;
1863 end
1864 end
1865 else
1866 begin
1867 Result := FLine[FLineIndex];
1868 Inc(FLineIndex);
1869 end;
1870 end;
1871
1872 constructor TInteractiveSQLStatementReader.Create(aPrompt: string; aContinue: string);
1873 begin
1874 inherited Create;
1875 FPrompt := aPrompt;
1876 FLineIndex := 0;
1877 FNextStatement := true;
1878 FContinuePrompt := aContinue;
1879 end;
1880
1881 function TInteractiveSQLStatementReader.GetNextStatement(var stmt: string
1882 ): boolean;
1883 begin
1884 Result := inherited GetNextStatement(stmt);
1885 FNextStatement := Result;
1886 end;
1887
1888 { TBatchSQLStatementReader }
1889
1890 function TBatchSQLStatementReader.GetChar: char;
1891 begin
1892 if not EOF and assigned(FInStream) and not (FInStream.Position = FInStream.Size) then
1893 begin
1894 Result := char(FInStream.ReadByte);
1895 if Result = LF then
1896 begin
1897 EchoNextLine(FCurLine);
1898 FCurLine := '';
1899 if assigned(OnProgressEvent) then
1900 OnProgressEvent(self,false,FIndex+1);
1901 Inc(FLineIndex);
1902 FIndex := 1;
1903 end
1904 else
1905 if Result <> CR then
1906 begin
1907 FCurLine += Result;
1908 Inc(FIndex);
1909 end;
1910 end
1911 else
1912 Result := #0;
1913 end;
1914
1915 function TBatchSQLStatementReader.GetErrorPrefix: string;
1916 begin
1917 Result := Format(sOnLineError,[FLineIndex,FIndex]);
1918 end;
1919
1920 procedure TBatchSQLStatementReader.Reset;
1921 begin
1922 inherited Reset;
1923 if FOwnsInStream and assigned(FInStream) then
1924 FInStream.Free;
1925 FInStream := nil;
1926 FOwnsInStream := false;
1927 FLineIndex := 1;
1928 FIndex := 1;
1929 FCurLine := '';
1930 end;
1931
1932 procedure TBatchSQLStatementReader.SetStreamSource(Lines: TStrings);
1933 begin
1934 Reset;
1935 FInStream := TMemoryStream.Create;
1936 FOwnsInStream := true;
1937 Lines.SaveToStream(FInStream);
1938 FInStream.Position := 0;
1939 if assigned(OnProgressEvent) then
1940 OnProgressEvent(self,true,FInStream.Size);
1941 end;
1942
1943 procedure TBatchSQLStatementReader.SetStreamSource(S: TStream);
1944 begin
1945 Reset;
1946 FInStream := S;
1947 if assigned(OnProgressEvent) then
1948 OnProgressEvent(self,true,S.Size - S.Position);
1949 end;
1950
1951 procedure TBatchSQLStatementReader.SetStreamSource(FileName: string);
1952 begin
1953 Reset;
1954 FInStream := TFileStream.Create(FileName,fmShareCompat);
1955 FOwnsInStream := true;
1956 if assigned(OnProgressEvent) then
1957 OnProgressEvent(self,true,FInStream.Size);
1958 end;
1959
1960 procedure TBatchSQLStatementReader.SetStringStreamSource(S: string);
1961 begin
1962 Reset;
1963 FInStream := TStringStream.Create(S);
1964 FOwnsInStream := true;
1965 if assigned(OnProgressEvent) then
1966 OnProgressEvent(self,true,FInStream.Size);
1967 end;
1968
1969 end.
1970