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