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