ViewVC Help
View File | Revision Log | Show Annotations | Download File | View Changeset | Root Listing
root/public/ibx/trunk/runtime/nongui/ibxscript.pas
Revision: 348
Committed: Wed Oct 6 09:38:14 2021 UTC (3 years, 1 month ago) by tony
Content type: text/x-pascal
File size: 56403 byte(s)
Log Message:
Fixes Merged

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