ViewVC Help
View File | Revision Log | Show Annotations | Download File | View Changeset | Root Listing
root/public/ibx/trunk/runtime/nongui/ibxscript.pas
Revision: 402
Committed: Mon Aug 1 10:07:24 2022 UTC (2 years, 3 months ago) by tony
Content type: text/x-pascal
File size: 36853 byte(s)
Log Message:
IBX Release 2.5.0

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 tony 209
40 tony 263 { TSQLStatementReader }
41 tony 209
42 tony 263 TSQLStatementReader = class(TSQLXMLReader)
43 tony 209 private
44 tony 263 type
45     TSQLState = (stDefault, stInStmt, stInBlock, stInArrayDim, stInDeclare);
46     private
47 tony 402 FDatabase: TIBDatabase;
48 tony 263 FHasBegin: boolean;
49 tony 209 FOnNextLine: TOnNextLine;
50     FTerminator: char;
51 tony 402 FTransaction: TIBTransaction;
52 tony 209 protected
53 tony 263 procedure EchoNextLine(aLine: string);
54 tony 402 function GetAttachment: IAttachment; override;
55     function GetTransaction: ITransaction; override;
56 tony 209 public
57     constructor Create;
58 tony 263 function GetNextStatement(var stmt: string) : boolean; virtual;
59     property HasBegin: boolean read FHasBegin;
60     property Terminator: char read FTerminator write FTerminator default DefaultTerminator;
61 tony 209 property OnNextLine: TOnNextLine read FOnNextLine write FOnNextLine;
62 tony 402 property Database: TIBDatabase read FDatabase write FDatabase;
63     property Transaction: TIBTransaction read FTransaction write FTransaction;
64 tony 209 end;
65    
66    
67 tony 263 { TBatchSQLStatementReader }
68    
69     {This SQL Reader supports non-interactive parsing of a text file, stream or
70 tony 209 lines of text.}
71    
72 tony 263 TBatchSQLStatementReader = class(TSQLStatementReader)
73 tony 209 private
74 tony 263 FInStream: TStream;
75     FOwnsInStream: boolean;
76 tony 209 FLineIndex: integer;
77 tony 263 FIndex: integer;
78     FCurLine: string;
79 tony 209 protected
80 tony 402 function GetChar: AnsiChar; override;
81     function GetErrorPrefix: AnsiString; override;
82 tony 209 public
83 tony 263 procedure Reset; override;
84 tony 209 procedure SetStreamSource(Lines: TStrings); overload;
85     procedure SetStreamSource(S: TStream); overload;
86     procedure SetStreamSource(FileName: string); overload;
87 tony 263 procedure SetStringStreamSource(S: string);
88 tony 209 end;
89    
90 tony 263 { TInteractiveSQLStatementReader }
91 tony 209
92 tony 263 {This SQL reader supports interactive parsing of commands and
93 tony 209 SQL statements entered at a console}
94    
95 tony 263 TInteractiveSQLStatementReader = class(TSQLStatementReader)
96 tony 209 private
97     FPrompt: string;
98     FContinuePrompt: string;
99     FTerminated: boolean;
100 tony 263 FLine: string;
101     FLineIndex: integer;
102     FNextStatement: boolean;
103     function GetNextLine(var Line: string):boolean;
104 tony 209 protected
105 tony 402 function GetChar: AnsiChar; override;
106     function GetErrorPrefix: AnsiString; override;
107 tony 209 public
108     constructor Create(aPrompt: string='SQL>'; aContinue: string = 'CON>');
109 tony 263 function GetNextStatement(var stmt: string) : boolean; override;
110 tony 209 property Terminated: boolean read FTerminated write FTerminated;
111     end;
112    
113     TGetParamValue = procedure(Sender: TObject; ParamName: string; var BlobID: TISC_QUAD) of object;
114     TLogEvent = procedure(Sender: TObject; Msg: string) of Object;
115     TOnSelectSQL = procedure (Sender: TObject; SQLText: string) of object;
116     TOnSetStatement = procedure(Sender: TObject; command, aValue, stmt: string; var Done: boolean) of object;
117     TOnCreateDatabase = procedure (Sender: TObject; var DatabaseFileName: string) of object;
118    
119     { TCustomIBXScript }
120    
121     {This is the main script processing engine and can be customised by subclassing
122     and defining the symbol stream appropriate for use.
123    
124     The RunScript function is used to invoke the processing of a symbol stream. Each
125     SQL statement is extracted one by one. If it is recognised as a built in command
126     by "ProcessStatement" then it is actioned directly. Otherwise, it is executed
127     using the TIBSQL component. Note that SQL validation by this class is only partial
128     and is sufficient only to parse the SQL into statements. The Firebird engine does
129     the rest when the statement is executed.}
130    
131     TCustomIBXScript = class(TComponent)
132     private
133     FEcho: boolean;
134 tony 263 FSQLReader: TSQLStatementReader;
135 tony 209 FDatabase: TIBDatabase;
136     FDataOutputFormatter: TIBCustomDataOutput;
137     FIgnoreCreateDatabase: boolean;
138     FIgnoreGrants: boolean;
139     FOnCreateDatabase: TOnCreateDatabase;
140     FOnErrorLog: TLogEvent;
141     FOnSelectSQL: TOnSelectSQL;
142     FOnSetStatement: TOnSetStatement;
143     FShowAffectedRows: boolean;
144     FShowPerformanceStats: boolean;
145     FStopOnFirstError: boolean;
146     FTransaction: TIBTransaction;
147     FInternalTransaction: TIBTransaction;
148     FISQL: TIBSQL;
149     FGetParamValue: TGetParamValue;
150     FOnOutputLog: TLogEvent;
151     FAutoDDL: boolean;
152     procedure DoCommit;
153     procedure DoReconnect;
154     function GetOnProgressEvent: TOnProgressEvent;
155     function GetTransaction: TIBTransaction;
156     procedure SetDatabase(AValue: TIBDatabase);
157     procedure SetDataOutputFormatter(AValue: TIBCustomDataOutput);
158     procedure SetOnProgressEvent(AValue: TOnProgressEvent);
159     procedure SetParamValue(SQLVar: ISQLParam);
160     procedure SetShowPerformanceStats(AValue: boolean);
161     procedure SetTransaction(AValue: TIBTransaction);
162     protected
163     procedure Add2Log(const Msg: string; IsError: boolean=true); virtual;
164 tony 228 procedure ExecSQL(stmt: string);
165 tony 209 procedure EchoNextLine(Sender: TObject; Line: string);
166     procedure Notification(AComponent: TComponent; Operation: TOperation); override;
167     function ProcessStatement(stmt: string): boolean; virtual;
168     function ProcessStream: boolean;
169 tony 263 procedure SetSQLStatementReader(SQLStatementReader: TSQLStatementReader);
170 tony 209 public
171     constructor Create(aOwner: TComponent); override;
172     destructor Destroy; override;
173     procedure DefaultSelectSQLHandler(aSQLText: string);
174 tony 263 property SQLStatementReader: TSQLStatementReader read FSQLReader;
175 tony 209 published
176     property Database: TIBDatabase read FDatabase write SetDatabase;
177     property DataOutputFormatter: TIBCustomDataOutput read FDataOutputFormatter
178     write SetDataOutputFormatter;
179     property AutoDDL: boolean read FAutoDDL write FAutoDDL default true;
180     property Echo: boolean read FEcho write FEcho default true; {Echo Input to Log}
181     property IgnoreGrants: boolean read FIgnoreGrants write FIgnoreGrants;
182     property IgnoreCreateDatabase: boolean read FIgnoreCreateDatabase write FIgnoreCreateDatabase;
183     property Transaction: TIBTransaction read GetTransaction write SetTransaction;
184     property ShowAffectedRows: boolean read FShowAffectedRows write FShowAffectedRows;
185     property ShowPerformanceStats: boolean read FShowPerformanceStats write SetShowPerformanceStats;
186     property StopOnFirstError: boolean read FStopOnFirstError write FStopOnFirstError default true;
187     property GetParamValue: TGetParamValue read FGetParamValue write FGetParamValue; {resolve parameterized queries}
188     property OnOutputLog: TLogEvent read FOnOutputLog write FOnOutputLog; {Log handler}
189     property OnErrorLog: TLogEvent read FOnErrorLog write FOnErrorLog;
190     property OnProgressEvent: TOnProgressEvent read GetOnProgressEvent write SetOnProgressEvent; {Progress Bar Support}
191     property OnSelectSQL: TOnSelectSQL read FOnSelectSQL write FOnSelectSQL; {Handle Select SQL Statements}
192     property OnSetStatement: TOnSetStatement read FOnSetStatement write FOnSetStatement;
193     property OnCreateDatabase: TOnCreateDatabase read FOnCreateDatabase write FOnCreateDatabase;
194     end;
195    
196     {
197     TIBXScript: runs an SQL script in the specified file or stream. The text is parsed
198     into SQL statements which are executed in turn. The intention is to be ISQL
199     compatible but with extensions:
200    
201     * All DML and DDL Statements are supported.
202    
203     * CREATE DATABASE, DROP DATABASE, CONNECT and COMMIT are supported.
204    
205     * The following SET statements are supported:
206     SET SQL DIALECT
207     SET TERM
208     SET AUTODDL
209     SET BAIL
210     SET ECHO
211     SET COUNT
212     SET STATS
213     SET NAMES <character set>
214    
215     * New Command: RECONNECT. Performs a commit followed by disconnecting and
216     reconnecting to the database.
217    
218     * Procedure Bodies (BEGIN .. END blocks) are self-delimiting and do not need
219     an extra terminator. If a terminator is present, this is treated as an
220     empty statement. The result is ISQL compatible, but does not require the
221     use of SET TERM.
222    
223     * DML statements may have arguments in IBX format (e.g UPDATE MYTABLE Set data = :mydata).
224     Arguments are valid only for BLOB columns and are resolved using the GetParamValue
225     event. This returns the blobid to be used. A typical use of the event is to
226     read binary data from a file, save it in a blob stream and return the blob id.
227    
228     Select SQL statements are not directly supported but can be handled by an external
229     handler (OnSelectSQL event). If the handler is not present then an exception
230     is raised if a Select SQL statement is found.
231    
232     Properties:
233    
234     * Database: Link to TIBDatabase component
235     * Transaction: Link to Transaction. Defaults to internaltransaction (concurrency, wait)
236     * AutoDDL: When true DDL statements are automatically committed after execution
237     * Echo: boolean. When true, all SQL statements are echoed to log
238     * StopOnFirstError: boolean. When true the script engine terminates on the first
239     SQL Error.
240     * IgnoreGrants: When true, grant statements are silently discarded. This can be
241     useful when applying a script using the Embedded Server.
242     * ShowPerformanceStats: When true, performance statistics (in ISQL format) are
243     written to the log after a DML statement is executed
244     * DataOutputFormatter: Identifies a Data Output Formatter component used to format
245     the results of executing a Select Statement
246    
247    
248     Events:
249    
250     * GetParamValue: called when an SQL parameter is found (in PSQL :name format).
251     This is only called for blob fields. Handler should return the BlobID to be
252     used as the parameter value. If not present an exception is raised when a
253     parameter is found.
254     * OnOutputLog: Called to write SQL Statements to the log (stdout)
255     * OnErrorLog: Called to write all other messages to the log (stderr)
256     * OnProgressEvent: Progress bar support. If Reset is true the value is maximum
257     value of progress bar. Otherwise called to step progress bar.
258     * OnSelectSQL: handler for select SQL statements. If not present, select SQL
259     statements result in an exception.
260     * OnSetStatement: called to process a SET command that has not already been
261     handled by TIBXScript.
262    
263     The RunScript function is used to execute an SQL Script and may be called
264     multiple times.
265     }
266    
267     { TIBXScript }
268    
269     TIBXScript = class(TCustomIBXScript)
270     public
271     constructor Create(aOwner: TComponent); override;
272     {use RunScript instead of PerformUpdate}
273     function PerformUpdate(SQLFile: string; aAutoDDL: boolean): boolean; overload; deprecated;
274     function PerformUpdate(SQLStream: TStream; aAutoDDL: boolean): boolean; overload; deprecated;
275     function RunScript(SQLFile: string): boolean; overload;
276     function RunScript(SQLStream: TStream): boolean; overload;
277     function RunScript(SQLLines: TStrings): boolean; overload;
278     function ExecSQLScript(sql: string): boolean;
279     end;
280    
281    
282     resourcestring
283     sInvalidSetStatement = 'Invalid %s Statement - %s';
284 tony 402 sInvalidCharacterSet = 'Unrecognised character set name - "%s"';
285     sOnLineError = 'On Line %d Character %d: ';
286 tony 209
287     implementation
288    
289     uses Sysutils, RegExpr;
290    
291     resourcestring
292     sNoSelectSQL = 'Select SQL Statements are not supported';
293     sNoParamQueries = 'Parameterised Queries are not supported';
294     sResolveQueryParam = 'Resolving Query Parameter: %s';
295     sStatementError = 'Error processing SQL statement: %s %s - for statement "%s"';
296    
297 tony 402 { TSQLStatementReader }
298 tony 209
299 tony 402 procedure TSQLStatementReader.EchoNextLine(aLine: string);
300 tony 209 begin
301 tony 402 if assigned(FOnNextLine) then
302     OnNextLine(self,aLine);
303 tony 209 end;
304    
305 tony 402 function TSQLStatementReader.GetAttachment: IAttachment;
306 tony 209 begin
307 tony 402 if FDatabase <> nil then
308     Result := FDatabase.Attachment
309     else
310     Result := nil;
311 tony 209 end;
312    
313 tony 402 function TSQLStatementReader.GetTransaction: ITransaction;
314 tony 263 begin
315 tony 402 if FTransaction <> nil then
316     Result := FTransaction.TransactionIntf
317     else
318     Result := nil;
319 tony 263 end;
320 tony 209
321 tony 263 constructor TSQLStatementReader.Create;
322     begin
323     inherited Create;
324     Terminator := DefaultTerminator;
325     end;
326    
327     function TSQLStatementReader.GetNextStatement(var stmt: string): boolean;
328     var State: TSQLState;
329     Nested: integer;
330     token: TSQLTokens;
331     EndOfStatement: boolean;
332     begin
333     FHasBegin := false;
334     EndOfStatement := false;
335     Nested := 0;
336     stmt := '';
337     State := stDefault;
338     while not EOF and not EndOfStatement do
339     begin
340     token := GetNextToken;
341     // writeln(token,' ',TokenText,' ',Terminator);
342     case State of
343     stDefault:
344     {ignore everything before a reserved word}
345     if (token <= high(TSQLReservedWords)) or (token = sqltIdentifier) then
346     begin
347     State := stInStmt;
348     stmt += TokenText;
349     end;
350    
351     stInStmt:
352     begin
353     case token of
354     sqltBegin:
355     begin
356     FHasBegin := true;
357     State := stInBlock;
358     Nested := 1;
359     stmt += TokenText;
360     end;
361    
362     sqltDeclare:
363     begin
364     State := stInDeclare;
365     stmt += TokenText;
366     end;
367    
368     sqltOpenSquareBracket:
369     begin
370     State := stInArrayDim;
371     stmt += TokenText;
372     end;
373    
374     sqltComment:
375     stmt += '/*' + TokenText + '*/';
376    
377     sqltCommentLine:
378     stmt += '/*' + TokenText + ' */' + LineEnding;
379    
380     sqltQuotedString:
381     stmt += '''' + SQLSafeString(TokenText) + '''';
382    
383     sqltIdentifierInDoubleQuotes:
384     stmt += '"' + TokenText + '"';
385    
386     sqltEOL:
387     stmt += LineEnding;
388    
389     else
390     begin
391     if (tokentext = Terminator) and (Nested = 0) then
392     begin
393     EndOfStatement := true;
394     State := stDefault;
395     end
396     else
397     stmt += TokenText;
398     end;
399     end;
400     end;
401    
402     {ignore begin..end blocks for Terminator detection }
403    
404     stInBlock:
405     begin
406     case token of
407     sqltBegin:
408     begin
409     Inc(Nested);
410     stmt += TokenText;
411     end;
412    
413     sqltEnd:
414     begin
415     Dec(Nested);
416     stmt += TokenText;
417     if Nested = 0 then
418     begin
419     State := stDefault;
420     EndOfStatement := true;
421     end;
422     end;
423    
424 tony 348 sqltCase:
425     {case constructs can appear within select statement in nested blocks.
426     We need to match the case constructs END token in order to parse the
427     block correctly. This is a simple parser and the only objective is
428     to determine the correct end of block. We therefore do not check to
429     ensure that the next end properly matches the case. The CASE is thus
430     treated the same as BEGIN. The Firebird SQL Parser will flag any errors
431     due to mismatched CASE/BEGIN END}
432     begin
433     Inc(Nested);
434     stmt += TokenText;
435     end;
436    
437 tony 263 sqltComment:
438     stmt += '/*' + TokenText + '*/';
439    
440     sqltCommentLine:
441     stmt += '/* ' + TokenText + ' */' + LineEnding;
442    
443     sqltQuotedString:
444     stmt += '''' + SQLSafeString(TokenText) + '''';
445    
446     sqltIdentifierInDoubleQuotes:
447     stmt += '"' + TokenText + '"';
448    
449     sqltEOL:
450     stmt += LineEnding;
451    
452     else
453     stmt += TokenText;
454     end;
455     end;
456    
457     {ignore array dimensions for Terminator detection }
458    
459     stInArrayDim:
460     begin
461     case token of
462    
463     sqltComment:
464     stmt += '/*' + TokenText + '*/';
465    
466     sqltCommentLine:
467     stmt += '/* ' + TokenText + ' */' + LineEnding;
468    
469     sqltCloseSquareBracket:
470     begin
471     stmt += TokenText;
472     State := stInStmt;
473     end;
474    
475     sqltEOL:
476     stmt += LineEnding;
477    
478     else
479     stmt += TokenText;
480     end;
481     end;
482    
483     {ignore Declare statement for terminator - semi-colon terminates declaration}
484    
485     stInDeclare:
486     begin
487     case token of
488    
489     sqltComment:
490     stmt += '/*' + TokenText + '*/';
491    
492     sqltCommentLine:
493     stmt += '/* ' + TokenText + ' */' + LineEnding;
494    
495 tony 349 sqltQuotedString:
496     stmt += '''' + SQLSafeString(TokenText) + ''''; {exists some DECLARE with cursor having SELECT ...\... rc.rdb$constraint_type = 'PRIMARY KEY');}
497    
498 tony 263 sqltSemiColon:
499     begin
500     State := stInStmt;
501     stmt += TokenText;
502     end;
503    
504     sqltEOL:
505     stmt += LineEnding;
506    
507     else
508     stmt += TokenText;
509     end;
510     end;
511     end;
512 tony 402 // writeln(stmt);
513 tony 263 end;
514     Result := stmt <> '';
515     end;
516    
517    
518    
519 tony 209 { TIBXScript }
520    
521     constructor TIBXScript.Create(aOwner: TComponent);
522     begin
523     inherited Create(aOwner);
524 tony 263 SetSQLStatementReader(TBatchSQLStatementReader.Create);
525 tony 209 end;
526    
527     function TIBXScript.PerformUpdate(SQLFile: string; aAutoDDL: boolean): boolean;
528     begin
529     FAutoDDL := aAutoDDL;
530     Result := RunScript( SQLFile);
531     end;
532    
533     function TIBXScript.PerformUpdate(SQLStream: TStream; aAutoDDL: boolean
534     ): boolean;
535     begin
536     FAutoDDL := aAutoDDL;
537     Result := RunScript(SQLStream);
538     end;
539    
540     function TIBXScript.RunScript(SQLFile: string): boolean;
541     begin
542 tony 263 TBatchSQLStatementReader(FSQLReader).SetStreamSource(SQLFile);
543 tony 209 Result := ProcessStream;
544     end;
545    
546     function TIBXScript.RunScript(SQLStream: TStream): boolean;
547     begin
548 tony 263 TBatchSQLStatementReader(FSQLReader).SetStreamSource(SQLStream);
549 tony 209 Result := ProcessStream;
550     end;
551    
552     function TIBXScript.RunScript(SQLLines: TStrings): boolean;
553     begin
554 tony 263 TBatchSQLStatementReader(FSQLReader).SetStreamSource(SQLLines);
555 tony 209 Result := ProcessStream;
556     end;
557    
558     function TIBXScript.ExecSQLScript(sql: string): boolean;
559     begin
560 tony 263 TBatchSQLStatementReader(FSQLReader).SetStringStreamSource(sql);
561     Result := ProcessStream;
562 tony 209 end;
563    
564     { TCustomIBXScript }
565    
566     procedure TCustomIBXScript.Add2Log(const Msg: string; IsError: boolean);
567     begin
568     if IsError then
569     begin
570     if assigned(OnErrorLog) then OnErrorLog(self,Msg)
571     end
572     else
573     if assigned(FOnOutputLog) then FOnOutputLog(self,Msg)
574     end;
575    
576     procedure TCustomIBXScript.DoCommit;
577     begin
578     with GetTransaction do
579     if InTransaction then Commit;
580     end;
581    
582     procedure TCustomIBXScript.DoReconnect;
583     begin
584     with GetTransaction do
585     if InTransaction then Commit;
586 tony 229 Database.Reconnect;
587 tony 209 end;
588    
589     procedure TCustomIBXScript.ExecSQL(stmt: string);
590     var DDL: boolean;
591     I: integer;
592     begin
593     Database.Connected := true;
594     FISQL.SQL.Text := stmt;
595     FISQL.Transaction := GetTransaction;
596     FISQL.Transaction.Active := true;
597 tony 263 // FISQL.ParamCheck := not FSQLReader.HasBegin; {Probably PSQL}
598 tony 209 FISQL.Prepare;
599     FISQL.Statement.EnableStatistics(ShowPerformanceStats);
600    
601     if FISQL.SQLStatementType in [SQLInsert, SQLUpdate, SQLDelete] then
602     begin
603     {Interpret parameters}
604     for I := 0 to FISQL.Params.Count - 1 do
605     SetParamValue(FISQL.Params[I]);
606     end;
607    
608     if FISQL.SQLStatementType = SQLSelect then
609     begin
610     if assigned(OnSelectSQL) then
611     OnSelectSQL(self,stmt)
612     else
613     DefaultSelectSQLHandler(stmt);
614     end
615     else
616     begin
617     DDL := FISQL.SQLStatementType = SQLDDL;
618     if not DDL or not FIgnoreGrants or (Pos('GRANT',AnsiUpperCase(Trim(stmt))) <> 1) then
619     begin
620     FISQL.ExecQuery;
621     if ShowAffectedRows and not DDL then
622     Add2Log('Rows Affected: ' + IntToStr(FISQL.RowsAffected));
623     if not DDL then
624     TIBCustomDataOutput.ShowPerfStats(FISQL.Statement,@Add2Log);
625     end;
626    
627     if FAutoDDL and DDL then
628     FISQL.Transaction.Commit;
629     FISQL.Close;
630     end;
631     FISQL.SQL.Clear;
632     end;
633    
634     function TCustomIBXScript.GetOnProgressEvent: TOnProgressEvent;
635     begin
636 tony 263 Result := FSQLReader.OnProgressEvent;
637 tony 209 end;
638    
639     function TCustomIBXScript.GetTransaction: TIBTransaction;
640     begin
641     if not (csDesigning in ComponentState) and (FTransaction = nil) then
642     Result := FInternalTransaction
643     else
644     Result := FTransaction;
645     end;
646    
647     procedure TCustomIBXScript.EchoNextLine(Sender: TObject; Line: string);
648     begin
649     if Echo then Add2Log(Line);
650     end;
651    
652     procedure TCustomIBXScript.Notification(AComponent: TComponent;
653     Operation: TOperation);
654     begin
655     inherited Notification(AComponent, Operation);
656     if (AComponent = FDatabase) and (Operation = opRemove) then
657     FDatabase := nil;
658     if (AComponent = FTransaction) and (Operation = opRemove) then
659     FTransaction := nil;
660     if (AComponent = DataOutputFormatter) and (Operation = opRemove) then
661     FDataOutputFormatter := nil;
662     end;
663    
664     procedure TCustomIBXScript.SetDatabase(AValue: TIBDatabase);
665     begin
666     if not (csLoading in ComponentState) and (FDatabase = AValue) then Exit;
667     FDatabase := AValue;
668     FISQL.Database := AValue;
669     FInternalTransaction.Active := false;
670     FInternalTransaction.DefaultDatabase := AValue;
671     end;
672    
673     procedure TCustomIBXScript.SetDataOutputFormatter(AValue: TIBCustomDataOutput);
674     begin
675     if FDataOutputFormatter = AValue then Exit;
676     if (FDataOutputFormatter <> nil) and (AValue <> nil) then
677     AValue.Assign(FDataOutputFormatter);
678     FDataOutputFormatter := AValue;
679     if FDataOutputFormatter <> nil then
680     FDataOutputFormatter.Database := Database;
681     end;
682    
683     procedure TCustomIBXScript.SetOnProgressEvent(AValue: TOnProgressEvent);
684     begin
685 tony 263 FSQLReader.OnProgressEvent := AValue;
686 tony 209 end;
687    
688     procedure TCustomIBXScript.SetParamValue(SQLVar: ISQLParam);
689     var BlobID: TISC_QUAD;
690     ix: integer;
691     begin
692 tony 263 if (SQLVar.SQLType = SQL_BLOB) and (Pos(TSQLXMLReader.ibx_blob,SQLVar.Name) = 1) then
693 tony 209 begin
694 tony 263 ix := StrToInt(system.copy(SQLVar.Name,length(TSQLXMLReader.ibx_blob)+1,length(SQLVar.Name)-length(TSQLXMLReader.ibx_blob)));
695     SQLVar.AsBlob := FSQLReader.BlobData[ix].BlobIntf;
696 tony 209 Exit;
697     end
698     else
699 tony 263 if (SQLVar.SQLType = SQL_ARRAY) and (Pos(TSQLXMLReader.ibx_array,SQLVar.Name) = 1) then
700 tony 209 begin
701 tony 263 ix := StrToInt(system.copy(SQLVar.Name,length(TSQLXMLReader.ibx_array)+1,length(SQLVar.Name)-length(TSQLXMLReader.ibx_array)));
702     SQLVar.AsArray := FSQLReader.ArrayData[ix].ArrayIntf;
703 tony 209 Exit;
704     end;
705    
706     if assigned(FGetParamValue) and (SQLVar.SQLType = SQL_BLOB) then
707     begin
708     Add2Log(Format(sResolveQueryParam,[SQLVar.Name]));
709     GetParamValue(self,SQLVar.Name,BlobID);
710     if (BlobID.gds_quad_high = 0) and (BlobID.gds_quad_low = 0) then
711     SQLVar.Clear
712     else
713     SQLVar.AsQuad := BlobID
714     end
715     else
716     raise Exception.Create(sNoParamQueries);
717     end;
718    
719     procedure TCustomIBXScript.SetShowPerformanceStats(AValue: boolean);
720     begin
721     if FShowPerformanceStats = AValue then Exit;
722     FShowPerformanceStats := AValue;
723     if assigned(DataOutputFormatter) then
724     DataOutputFormatter.ShowPerformanceStats := AValue;
725     end;
726    
727     function TCustomIBXScript.ProcessStream: boolean;
728     var stmt: string;
729     begin
730     Result := false;
731 tony 402 FSQLReader.Database := Database;
732     if FTransaction = nil then
733     FSQLReader.Transaction := FInternalTransaction
734     else
735     FSQLReader.Transaction := FTransaction;
736 tony 263 while FSQLReader.GetNextStatement(stmt) do
737 tony 209 try
738 tony 263 stmt := trim(stmt);
739 tony 349 // writeln('stmt = "',stmt,'"');
740 tony 263 if stmt = '' then continue;
741 tony 209 if not ProcessStatement(stmt) then
742     ExecSQL(stmt);
743    
744     except on E:Exception do
745     begin
746     with GetTransaction do
747     if InTransaction then Rollback;
748 tony 263 FSQLReader.Terminator := TSQLStatementReader.DefaultTerminator;
749 tony 209 if assigned(OnErrorLog) then
750     begin
751 tony 263 Add2Log(Format(sStatementError,[FSQLReader.GetErrorPrefix,
752 tony 209 E.Message,stmt]),true);
753     if StopOnFirstError then Exit;
754     end
755     else
756     raise;
757     end
758     end;
759     Result := true;
760     end;
761    
762 tony 263 procedure TCustomIBXScript.SetSQLStatementReader(
763     SQLStatementReader: TSQLStatementReader);
764     begin
765     FSQLReader := SQLStatementReader;
766     FSQLReader.OnNextLine := @EchoNextLine;
767     end;
768    
769 tony 209 function TCustomIBXScript.ProcessStatement(stmt: string): boolean;
770     var command: string;
771    
772     function Toggle(aValue: string): boolean;
773     begin
774     aValue := AnsiUpperCase(aValue);
775     if aValue = 'ON' then
776     Result := true
777     else
778     if aValue = 'OFF' then
779     Result := false
780     else
781     raise Exception.CreateFmt(sInvalidSetStatement, [command,stmt]);
782     end;
783    
784     procedure ExtractUserInfo;
785     var RegexObj: TRegExpr;
786     begin
787     RegexObj := TRegExpr.Create;
788     try
789     RegexObj.ModifierG := false; {turn off greedy matches}
790     RegexObj.Expression := ' +USER +''(.+)''';
791 tony 229 if RegexObj.Exec(stmt) then
792 tony 209 FDatabase.Params.Values['user_name'] := RegexObj.Match[1];
793    
794     RegexObj.Expression := ' +PASSWORD +''(.+)''';
795 tony 229 if RegexObj.Exec(stmt) then
796 tony 209 FDatabase.Params.Values['password'] :=
797     system.copy(stmt,RegexObj.MatchPos[1],RegexObj.MatchLen[1]);
798     finally
799     RegexObj.Free;
800     end;
801     end;
802    
803     procedure ExtractConnectInfo;
804     var RegexObj: TRegExpr;
805     begin
806     ExtractUserInfo;
807     RegexObj := TRegExpr.Create;
808     try
809     RegexObj.ModifierG := false; {turn off greedy matches}
810 tony 229 RegexObj.ModifierI := true; {case insensitive}
811 tony 209 RegexObj.Expression := '^ *CONNECT +''(.*)''';
812 tony 229 if RegexObj.Exec(stmt) then
813 tony 209 begin
814     FDatabase.DatabaseName := system.copy(stmt,RegexObj.MatchPos[1],RegexObj.MatchLen[1]);
815     end;
816    
817     RegexObj.Expression := ' +ROLE +''(.+)''';
818 tony 229 if RegexObj.Exec(stmt) then
819 tony 209 FDatabase.Params.Values['sql_role_name'] := RegexObj.Match[1]
820     else
821     with FDatabase.Params do
822     if IndexOfName('sql_role_name') <> -1 then
823     Delete(IndexOfName('sql_role_name'));
824    
825     RegexObj.Expression := ' +CACHE +([0-9]+)';
826 tony 229 if RegexObj.Exec(stmt) then
827 tony 209 FDatabase.Params.Values['cache_manager'] := RegexObj.Match[1]
828     else
829     with FDatabase.Params do
830     if IndexOfName('cache_manager') <> -1 then
831     Delete(IndexOfName('cache_manager'));
832     finally
833     RegexObj.Free;
834     end;
835     end;
836    
837     procedure UpdateUserPassword;
838     var RegexObj: TRegExpr;
839     begin
840     RegexObj := TRegExpr.Create;
841     try
842     RegexObj.ModifierG := false; {turn off greedy matches}
843 tony 229 RegexObj.ModifierI := true; {case insensitive}
844 tony 209 RegexObj.Expression := '^ *CREATE +(DATABASE|SCHEMA) +(''.*'') +USER +''(.+)''';
845 tony 229 if not RegexObj.Exec(stmt) and (FDatabase.Params.IndexOfName('user_name') <> -1) then
846 tony 209 begin
847     RegexObj.Expression := '^ *CREATE +(DATABASE|SCHEMA) +(''.*'')';
848 tony 229 if RegexObj.Exec(stmt) then
849 tony 209 begin
850     system.Insert(' USER ''' + FDatabase.Params.Values['user_name'] +'''',stmt,
851     RegexObj.MatchPos[2] + RegexObj.MatchLen[2]);
852     end;
853     end;
854    
855     RegexObj.Expression := '^ *CREATE +(DATABASE|SCHEMA) +''.*'' +USER +''.+'' PASSWORD +''(.+)''';
856 tony 229 if not RegexObj.Exec(stmt) and (FDatabase.Params.IndexOfName('password') <> -1) then
857 tony 209 begin
858     RegexObj.Expression := '^ *CREATE +(DATABASE|SCHEMA) +''.*'' +(USER +''.+'')';
859 tony 229 if RegexObj.Exec(stmt) then
860 tony 209 begin
861     system.Insert(' PASSWORD ''' + FDatabase.Params.Values['password'] +'''',stmt,
862     RegexObj.MatchPos[2] + RegexObj.MatchLen[2]);
863     end;
864     end;
865     finally
866     RegexObj.Free;
867     end;
868     end;
869    
870     var RegexObj: TRegExpr;
871     n: integer;
872     charsetid: integer;
873     param: string;
874     Terminator: char;
875     FileName: string;
876     DBConnected: boolean;
877     LoginPrompt: boolean;
878     begin
879     Result := false;
880 tony 263 Terminator := FSQLReader.Terminator;
881 tony 209 RegexObj := TRegExpr.Create;
882     try
883     {process create database}
884 tony 229 RegexObj.ModifierI := true; {case insensitive}
885 tony 209 RegexObj.Expression := '^ *CREATE +(DATABASE|SCHEMA) +''(.*)''(.*)(\' + Terminator + '|)';
886 tony 229 if RegexObj.Exec(stmt) then
887 tony 209 begin
888     if IgnoreCreateDatabase then
889     begin
890     Result := true;
891     Exit;
892     end;
893     FileName := system.copy(stmt,RegexObj.MatchPos[2], RegexObj.MatchLen[2]);
894     if assigned(FOnCreateDatabase) then
895     OnCreateDatabase(self,FileName);
896     stmt := 'CREATE DATABASE ''' + FileName + '''' + system.copy(stmt,RegexObj.MatchPos[3], RegexObj.MatchLen[3]);
897     UpdateUserPassword;
898 tony 272 if FDatabase.Connected then
899     FDatabase.Dropdatabase;
900 tony 209 FDatabase.CreateDatabase(stmt);
901     Result := true;
902     Exit;
903     end;
904    
905     {process connect statement}
906     RegexObj.Expression := '^ *CONNECT +.*(\' + Terminator + '|)';
907 tony 229 if RegexObj.Exec(stmt) then
908 tony 209 begin
909     ExtractConnectInfo;
910 tony 229 FDatabase.Connected := false;
911     FDatabase.Connected := true;
912 tony 209 Result := true;
913     Exit;
914     end;
915    
916     {Process Drop Database}
917     RegexObj.Expression := '^ *DROP +DATABASE *(\' + Terminator + '|)';
918 tony 229 if RegexObj.Exec(stmt) then
919 tony 209 begin
920     FDatabase.DropDatabase;
921     Result := true;
922     Exit;
923     end;
924    
925     {process commit statement}
926     RegexObj.Expression := '^ *COMMIT *(\' + Terminator + '|)';
927 tony 229 if RegexObj.Exec(stmt) then
928 tony 209 begin
929     DoCommit;
930     Result := true;
931     Exit;
932     end;
933    
934     {process Reconnect statement}
935     RegexObj.Expression := '^ *RECONNECT *(\' + Terminator + '|)';
936 tony 229 if RegexObj.Exec(stmt) then
937 tony 209 begin
938     DoReconnect;
939     Result := true;
940     Exit;
941     end;
942    
943    
944     {Process Set Term}
945     RegexObj.Expression := '^ *SET +TERM +(.) *(\' + Terminator + '|)';
946 tony 229 if RegexObj.Exec(stmt) then
947 tony 209 begin
948 tony 263 FSQLReader.Terminator := RegexObj.Match[1][1];
949 tony 209 Result := true;
950     Exit;
951     end;
952    
953     {process Set SQL Dialect}
954     RegexObj.Expression := '^ *SET +SQL +DIALECT +([0-9]) *(\' + Terminator + '|)';
955 tony 229 if RegexObj.Exec(stmt) then
956 tony 209 begin
957     n := StrToInt(RegexObj.Match[1]);
958     if Database.SQLDialect <> n then
959     begin
960     Database.SQLDialect := n;
961     if Database.Connected then
962     DoReconnect;
963     end;
964     Result := true;
965     Exit;
966     end;
967    
968     {Process Remaining Set statements}
969     RegexObj.Expression := '^ *SET +([A-Z]+)( +[A-Z0-9]+|) *(\' + Terminator + '|)';
970 tony 229 if RegexObj.Exec(stmt) then
971 tony 209 begin
972     command := AnsiUpperCase(RegexObj.Match[1]);
973     param := trim(RegexObj.Match[2]);
974 tony 287 if command = 'GENERATOR' then
975     begin
976     Result := false;
977     Exit;
978     end;
979 tony 209 if command = 'AUTODDL' then
980     AutoDDL := ((RegexObj.MatchLen[2] = 0) and not AutoDDL) or
981     (RegexObj.MatchLen[2] > 0) and Toggle(param)
982     else
983     if command = 'BAIL' then
984     StopOnFirstError := ((RegexObj.MatchLen[2] = 0) and not StopOnFirstError) or
985     (RegexObj.MatchLen[2] > 0) and Toggle(param)
986     else
987     if command = 'ECHO' then
988     Echo := ((RegexObj.MatchLen[2] = 0) and not Echo) or
989     (RegexObj.MatchLen[2] > 0) and Toggle(param)
990     else
991     if command = 'COUNT' then
992     ShowAffectedRows := ((RegexObj.MatchLen[2] = 0) and not ShowAffectedRows) or
993     (RegexObj.MatchLen[2] > 0) and Toggle(param)
994     else
995     if command = 'STATS' then
996     ShowPerformanceStats := ((RegexObj.MatchLen[2] = 0) and not FShowPerformanceStats) or
997     (RegexObj.MatchLen[2] > 0) and Toggle(param)
998     else
999     if command = 'NAMES' then
1000     begin
1001     if Database.Attachment.CharSetName2CharSetID(param,charsetid) then
1002     begin
1003     DBConnected := Database.Connected;
1004     LoginPrompt := Database.LoginPrompt;
1005     Database.LoginPrompt := false;
1006     Database.Connected := false;
1007     Database.Params.Values['lc_ctype'] := param;
1008     Database.Connected := DBConnected;
1009     Database.LoginPrompt := LoginPrompt;
1010     end
1011     else
1012     raise Exception.CreateFmt(sInvalidCharacterSet, [param,stmt]);
1013     end
1014     else
1015     begin
1016     if assigned(DataOutputFormatter) then
1017     DataOutputFormatter.SetCommand(command,param,stmt,Result);
1018 tony 263 if not Result then
1019     begin
1020     if assigned(OnSetStatement) then
1021     OnSetStatement(self,command,param,stmt,Result)
1022     else
1023     raise Exception.CreateFmt(sInvalidSetStatement, [command,stmt]);
1024     end;
1025 tony 209 Exit;
1026     end;
1027     Result := true;
1028     Exit;
1029     end;
1030    
1031     finally
1032     RegexObj.Free;
1033     end;
1034     end;
1035    
1036     procedure TCustomIBXScript.SetTransaction(AValue: TIBTransaction);
1037     begin
1038     if FTransaction = AValue then Exit;
1039     FTransaction := AValue;
1040     end;
1041    
1042     constructor TCustomIBXScript.Create(aOwner: TComponent);
1043     begin
1044     inherited Create(aOwner);
1045     FStopOnFirstError := true;
1046     FEcho := true;
1047     FAutoDDL := true;
1048     FISQL := TIBSQL.Create(self);
1049     FISQL.ParamCheck := true;
1050     FInternalTransaction := TIBTransaction.Create(self);
1051     FInternalTransaction.Params.Clear;
1052     FInternalTransaction.Params.Add('concurrency');
1053     FInternalTransaction.Params.Add('wait');
1054     end;
1055    
1056     destructor TCustomIBXScript.Destroy;
1057     begin
1058 tony 263 if FSQLReader <> nil then FSQLReader.Free;
1059 tony 209 if FISQL <> nil then FISQL.Free;
1060     if FInternalTransaction <> nil then FInternalTransaction.Free;
1061     inherited Destroy;
1062     end;
1063    
1064     procedure TCustomIBXScript.DefaultSelectSQLHandler(aSQLText: string);
1065     begin
1066     if assigned(DataOutputFormatter) then
1067     DataOutputFormatter.DataOut(aSQLText,@Add2Log)
1068     else
1069 tony 263 FSQLReader.ShowError(sNoSelectSQL);
1070 tony 209 end;
1071    
1072 tony 263 { TInteractiveSQLStatementReader }
1073 tony 209
1074 tony 402 function TInteractiveSQLStatementReader.GetErrorPrefix: AnsiString;
1075 tony 209 begin
1076 tony 263 Result := '';
1077 tony 209 end;
1078    
1079 tony 263 function TInteractiveSQLStatementReader.GetNextLine(var Line: string): boolean;
1080 tony 209 begin
1081 tony 263 if FNextStatement then
1082     write(FPrompt)
1083     else
1084     write(FContinuePrompt);
1085     Result := not system.EOF;
1086     if Result then
1087 tony 209 begin
1088 tony 263 readln(Line);
1089     EchoNextLine(Line);
1090 tony 209 end;
1091     end;
1092    
1093 tony 402 function TInteractiveSQLStatementReader.GetChar: AnsiChar;
1094 tony 209 begin
1095 tony 263 if Terminated then
1096     Result := #0
1097     else
1098     if FLineIndex > Length(FLine) then
1099 tony 209 begin
1100 tony 263 Result := LF;
1101     FLineIndex := 0;
1102     end
1103     else
1104     if FLineIndex = 0 then
1105 tony 209 begin
1106 tony 263 if not GetNextLine(FLine) then
1107     Result := #0
1108 tony 209 else
1109 tony 263 if Length(FLine) = 0 then
1110     Result := LF
1111 tony 209 else
1112     begin
1113 tony 263 Result := FLine[1];
1114     FLineIndex := 2;
1115     end
1116     end
1117     else
1118     begin
1119     Result := FLine[FLineIndex];
1120     Inc(FLineIndex);
1121 tony 209 end;
1122     end;
1123    
1124 tony 263 constructor TInteractiveSQLStatementReader.Create(aPrompt: string; aContinue: string);
1125 tony 209 begin
1126     inherited Create;
1127 tony 263 FPrompt := aPrompt;
1128     FLineIndex := 0;
1129     FNextStatement := true;
1130     FContinuePrompt := aContinue;
1131 tony 209 end;
1132    
1133 tony 263 function TInteractiveSQLStatementReader.GetNextStatement(var stmt: string
1134     ): boolean;
1135 tony 209 begin
1136 tony 263 Result := inherited GetNextStatement(stmt);
1137     FNextStatement := Result;
1138 tony 209 end;
1139    
1140 tony 263 { TBatchSQLStatementReader }
1141    
1142 tony 402 function TBatchSQLStatementReader.GetChar: AnsiChar;
1143 tony 209 begin
1144 tony 263 if not EOF and assigned(FInStream) and not (FInStream.Position = FInStream.Size) then
1145 tony 209 begin
1146 tony 263 Result := char(FInStream.ReadByte);
1147     if Result = LF then
1148 tony 209 begin
1149 tony 263 EchoNextLine(FCurLine);
1150     FCurLine := '';
1151     if assigned(OnProgressEvent) then
1152     OnProgressEvent(self,false,FIndex+1);
1153     Inc(FLineIndex);
1154     FIndex := 1;
1155     end
1156     else
1157 tony 353 if Result <> CR then
1158 tony 209 begin
1159 tony 263 FCurLine += Result;
1160     Inc(FIndex);
1161 tony 209 end;
1162 tony 263 end
1163 tony 209 else
1164 tony 263 Result := #0;
1165 tony 209 end;
1166    
1167 tony 402 function TBatchSQLStatementReader.GetErrorPrefix: AnsiString;
1168 tony 209 begin
1169     Result := Format(sOnLineError,[FLineIndex,FIndex]);
1170     end;
1171    
1172 tony 263 procedure TBatchSQLStatementReader.Reset;
1173 tony 209 begin
1174 tony 263 inherited Reset;
1175     if FOwnsInStream and assigned(FInStream) then
1176     FInStream.Free;
1177     FInStream := nil;
1178     FOwnsInStream := false;
1179     FLineIndex := 1;
1180     FIndex := 1;
1181 tony 315 FCurLine := '';
1182 tony 209 end;
1183    
1184 tony 263 procedure TBatchSQLStatementReader.SetStreamSource(Lines: TStrings);
1185 tony 209 begin
1186 tony 263 Reset;
1187     FInStream := TMemoryStream.Create;
1188     FOwnsInStream := true;
1189     Lines.SaveToStream(FInStream);
1190     FInStream.Position := 0;
1191     if assigned(OnProgressEvent) then
1192     OnProgressEvent(self,true,FInStream.Size);
1193 tony 209 end;
1194    
1195 tony 263 procedure TBatchSQLStatementReader.SetStreamSource(S: TStream);
1196 tony 209 begin
1197 tony 263 Reset;
1198     FInStream := S;
1199 tony 209 if assigned(OnProgressEvent) then
1200 tony 263 OnProgressEvent(self,true,S.Size - S.Position);
1201 tony 209 end;
1202    
1203 tony 263 procedure TBatchSQLStatementReader.SetStreamSource(FileName: string);
1204 tony 209 begin
1205 tony 263 Reset;
1206     FInStream := TFileStream.Create(FileName,fmShareCompat);
1207     FOwnsInStream := true;
1208 tony 209 if assigned(OnProgressEvent) then
1209 tony 263 OnProgressEvent(self,true,FInStream.Size);
1210 tony 209 end;
1211    
1212 tony 263 procedure TBatchSQLStatementReader.SetStringStreamSource(S: string);
1213 tony 209 begin
1214 tony 263 Reset;
1215     FInStream := TStringStream.Create(S);
1216     FOwnsInStream := true;
1217 tony 209 if assigned(OnProgressEvent) then
1218 tony 263 OnProgressEvent(self,true,FInStream.Size);
1219 tony 209 end;
1220    
1221     end.
1222    

Properties

Name Value
svn:eol-style native