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