ViewVC Help
View File | Revision Log | Show Annotations | Download File | View Changeset | Root Listing
root/public/ibx/trunk/runtime/nongui/ibxscript.pas
Revision: 315
Committed: Thu Feb 25 11:56:36 2021 UTC (3 years, 8 months ago) by tony
Content type: text/x-pascal
File size: 55772 byte(s)
Log Message:
Updated for IBX 4 release

File Contents

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