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