ViewVC Help
View File | Revision Log | Show Annotations | Download File | View Changeset | Root Listing
root/public/ibx/trunk/runtime/nongui/ibxscript.pas
Revision: 412
Committed: Mon Jul 17 14:08:12 2023 UTC (16 months ago) by tony
Content type: text/x-pascal
File size: 36932 byte(s)
Log Message:
Release 2.6.1

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 412 sqltIdentifierInDoubleQuotes:
499     stmt += '"' + TokenText + '"';
500    
501     sqltSemiColon:
502 tony 263 begin
503     State := stInStmt;
504     stmt += TokenText;
505     end;
506    
507     sqltEOL:
508     stmt += LineEnding;
509    
510     else
511     stmt += TokenText;
512     end;
513     end;
514     end;
515 tony 402 // writeln(stmt);
516 tony 263 end;
517     Result := stmt <> '';
518     end;
519    
520    
521    
522 tony 209 { TIBXScript }
523    
524     constructor TIBXScript.Create(aOwner: TComponent);
525     begin
526     inherited Create(aOwner);
527 tony 263 SetSQLStatementReader(TBatchSQLStatementReader.Create);
528 tony 209 end;
529    
530     function TIBXScript.PerformUpdate(SQLFile: string; aAutoDDL: boolean): boolean;
531     begin
532     FAutoDDL := aAutoDDL;
533     Result := RunScript( SQLFile);
534     end;
535    
536     function TIBXScript.PerformUpdate(SQLStream: TStream; aAutoDDL: boolean
537     ): boolean;
538     begin
539     FAutoDDL := aAutoDDL;
540     Result := RunScript(SQLStream);
541     end;
542    
543     function TIBXScript.RunScript(SQLFile: string): boolean;
544     begin
545 tony 263 TBatchSQLStatementReader(FSQLReader).SetStreamSource(SQLFile);
546 tony 209 Result := ProcessStream;
547     end;
548    
549     function TIBXScript.RunScript(SQLStream: TStream): boolean;
550     begin
551 tony 263 TBatchSQLStatementReader(FSQLReader).SetStreamSource(SQLStream);
552 tony 209 Result := ProcessStream;
553     end;
554    
555     function TIBXScript.RunScript(SQLLines: TStrings): boolean;
556     begin
557 tony 263 TBatchSQLStatementReader(FSQLReader).SetStreamSource(SQLLines);
558 tony 209 Result := ProcessStream;
559     end;
560    
561     function TIBXScript.ExecSQLScript(sql: string): boolean;
562     begin
563 tony 263 TBatchSQLStatementReader(FSQLReader).SetStringStreamSource(sql);
564     Result := ProcessStream;
565 tony 209 end;
566    
567     { TCustomIBXScript }
568    
569     procedure TCustomIBXScript.Add2Log(const Msg: string; IsError: boolean);
570     begin
571     if IsError then
572     begin
573     if assigned(OnErrorLog) then OnErrorLog(self,Msg)
574     end
575     else
576     if assigned(FOnOutputLog) then FOnOutputLog(self,Msg)
577     end;
578    
579     procedure TCustomIBXScript.DoCommit;
580     begin
581     with GetTransaction do
582     if InTransaction then Commit;
583     end;
584    
585     procedure TCustomIBXScript.DoReconnect;
586     begin
587     with GetTransaction do
588     if InTransaction then Commit;
589 tony 229 Database.Reconnect;
590 tony 209 end;
591    
592     procedure TCustomIBXScript.ExecSQL(stmt: string);
593     var DDL: boolean;
594     I: integer;
595     begin
596     Database.Connected := true;
597     FISQL.SQL.Text := stmt;
598     FISQL.Transaction := GetTransaction;
599     FISQL.Transaction.Active := true;
600 tony 263 // FISQL.ParamCheck := not FSQLReader.HasBegin; {Probably PSQL}
601 tony 209 FISQL.Prepare;
602     FISQL.Statement.EnableStatistics(ShowPerformanceStats);
603    
604     if FISQL.SQLStatementType in [SQLInsert, SQLUpdate, SQLDelete] then
605     begin
606     {Interpret parameters}
607     for I := 0 to FISQL.Params.Count - 1 do
608     SetParamValue(FISQL.Params[I]);
609     end;
610    
611     if FISQL.SQLStatementType = SQLSelect then
612     begin
613     if assigned(OnSelectSQL) then
614     OnSelectSQL(self,stmt)
615     else
616     DefaultSelectSQLHandler(stmt);
617     end
618     else
619     begin
620     DDL := FISQL.SQLStatementType = SQLDDL;
621     if not DDL or not FIgnoreGrants or (Pos('GRANT',AnsiUpperCase(Trim(stmt))) <> 1) then
622     begin
623     FISQL.ExecQuery;
624     if ShowAffectedRows and not DDL then
625     Add2Log('Rows Affected: ' + IntToStr(FISQL.RowsAffected));
626     if not DDL then
627     TIBCustomDataOutput.ShowPerfStats(FISQL.Statement,@Add2Log);
628     end;
629    
630     if FAutoDDL and DDL then
631     FISQL.Transaction.Commit;
632     FISQL.Close;
633     end;
634     FISQL.SQL.Clear;
635     end;
636    
637     function TCustomIBXScript.GetOnProgressEvent: TOnProgressEvent;
638     begin
639 tony 263 Result := FSQLReader.OnProgressEvent;
640 tony 209 end;
641    
642     function TCustomIBXScript.GetTransaction: TIBTransaction;
643     begin
644     if not (csDesigning in ComponentState) and (FTransaction = nil) then
645     Result := FInternalTransaction
646     else
647     Result := FTransaction;
648     end;
649    
650     procedure TCustomIBXScript.EchoNextLine(Sender: TObject; Line: string);
651     begin
652     if Echo then Add2Log(Line);
653     end;
654    
655     procedure TCustomIBXScript.Notification(AComponent: TComponent;
656     Operation: TOperation);
657     begin
658     inherited Notification(AComponent, Operation);
659     if (AComponent = FDatabase) and (Operation = opRemove) then
660     FDatabase := nil;
661     if (AComponent = FTransaction) and (Operation = opRemove) then
662     FTransaction := nil;
663     if (AComponent = DataOutputFormatter) and (Operation = opRemove) then
664     FDataOutputFormatter := nil;
665     end;
666    
667     procedure TCustomIBXScript.SetDatabase(AValue: TIBDatabase);
668     begin
669     if not (csLoading in ComponentState) and (FDatabase = AValue) then Exit;
670     FDatabase := AValue;
671     FISQL.Database := AValue;
672     FInternalTransaction.Active := false;
673     FInternalTransaction.DefaultDatabase := AValue;
674     end;
675    
676     procedure TCustomIBXScript.SetDataOutputFormatter(AValue: TIBCustomDataOutput);
677     begin
678     if FDataOutputFormatter = AValue then Exit;
679     if (FDataOutputFormatter <> nil) and (AValue <> nil) then
680     AValue.Assign(FDataOutputFormatter);
681     FDataOutputFormatter := AValue;
682     if FDataOutputFormatter <> nil then
683     FDataOutputFormatter.Database := Database;
684     end;
685    
686     procedure TCustomIBXScript.SetOnProgressEvent(AValue: TOnProgressEvent);
687     begin
688 tony 263 FSQLReader.OnProgressEvent := AValue;
689 tony 209 end;
690    
691     procedure TCustomIBXScript.SetParamValue(SQLVar: ISQLParam);
692     var BlobID: TISC_QUAD;
693     ix: integer;
694     begin
695 tony 263 if (SQLVar.SQLType = SQL_BLOB) and (Pos(TSQLXMLReader.ibx_blob,SQLVar.Name) = 1) then
696 tony 209 begin
697 tony 263 ix := StrToInt(system.copy(SQLVar.Name,length(TSQLXMLReader.ibx_blob)+1,length(SQLVar.Name)-length(TSQLXMLReader.ibx_blob)));
698     SQLVar.AsBlob := FSQLReader.BlobData[ix].BlobIntf;
699 tony 209 Exit;
700     end
701     else
702 tony 263 if (SQLVar.SQLType = SQL_ARRAY) and (Pos(TSQLXMLReader.ibx_array,SQLVar.Name) = 1) then
703 tony 209 begin
704 tony 263 ix := StrToInt(system.copy(SQLVar.Name,length(TSQLXMLReader.ibx_array)+1,length(SQLVar.Name)-length(TSQLXMLReader.ibx_array)));
705     SQLVar.AsArray := FSQLReader.ArrayData[ix].ArrayIntf;
706 tony 209 Exit;
707     end;
708    
709     if assigned(FGetParamValue) and (SQLVar.SQLType = SQL_BLOB) then
710     begin
711     Add2Log(Format(sResolveQueryParam,[SQLVar.Name]));
712     GetParamValue(self,SQLVar.Name,BlobID);
713     if (BlobID.gds_quad_high = 0) and (BlobID.gds_quad_low = 0) then
714     SQLVar.Clear
715     else
716     SQLVar.AsQuad := BlobID
717     end
718     else
719     raise Exception.Create(sNoParamQueries);
720     end;
721    
722     procedure TCustomIBXScript.SetShowPerformanceStats(AValue: boolean);
723     begin
724     if FShowPerformanceStats = AValue then Exit;
725     FShowPerformanceStats := AValue;
726     if assigned(DataOutputFormatter) then
727     DataOutputFormatter.ShowPerformanceStats := AValue;
728     end;
729    
730     function TCustomIBXScript.ProcessStream: boolean;
731     var stmt: string;
732     begin
733     Result := false;
734 tony 402 FSQLReader.Database := Database;
735     if FTransaction = nil then
736     FSQLReader.Transaction := FInternalTransaction
737     else
738     FSQLReader.Transaction := FTransaction;
739 tony 263 while FSQLReader.GetNextStatement(stmt) do
740 tony 209 try
741 tony 263 stmt := trim(stmt);
742 tony 349 // writeln('stmt = "',stmt,'"');
743 tony 263 if stmt = '' then continue;
744 tony 209 if not ProcessStatement(stmt) then
745     ExecSQL(stmt);
746    
747     except on E:Exception do
748     begin
749     with GetTransaction do
750     if InTransaction then Rollback;
751 tony 263 FSQLReader.Terminator := TSQLStatementReader.DefaultTerminator;
752 tony 209 if assigned(OnErrorLog) then
753     begin
754 tony 263 Add2Log(Format(sStatementError,[FSQLReader.GetErrorPrefix,
755 tony 209 E.Message,stmt]),true);
756     if StopOnFirstError then Exit;
757     end
758     else
759     raise;
760     end
761     end;
762     Result := true;
763     end;
764    
765 tony 263 procedure TCustomIBXScript.SetSQLStatementReader(
766     SQLStatementReader: TSQLStatementReader);
767     begin
768     FSQLReader := SQLStatementReader;
769     FSQLReader.OnNextLine := @EchoNextLine;
770     end;
771    
772 tony 209 function TCustomIBXScript.ProcessStatement(stmt: string): boolean;
773     var command: string;
774    
775     function Toggle(aValue: string): boolean;
776     begin
777     aValue := AnsiUpperCase(aValue);
778     if aValue = 'ON' then
779     Result := true
780     else
781     if aValue = 'OFF' then
782     Result := false
783     else
784     raise Exception.CreateFmt(sInvalidSetStatement, [command,stmt]);
785     end;
786    
787     procedure ExtractUserInfo;
788     var RegexObj: TRegExpr;
789     begin
790     RegexObj := TRegExpr.Create;
791     try
792     RegexObj.ModifierG := false; {turn off greedy matches}
793     RegexObj.Expression := ' +USER +''(.+)''';
794 tony 229 if RegexObj.Exec(stmt) then
795 tony 209 FDatabase.Params.Values['user_name'] := RegexObj.Match[1];
796    
797     RegexObj.Expression := ' +PASSWORD +''(.+)''';
798 tony 229 if RegexObj.Exec(stmt) then
799 tony 209 FDatabase.Params.Values['password'] :=
800     system.copy(stmt,RegexObj.MatchPos[1],RegexObj.MatchLen[1]);
801     finally
802     RegexObj.Free;
803     end;
804     end;
805    
806     procedure ExtractConnectInfo;
807     var RegexObj: TRegExpr;
808     begin
809     ExtractUserInfo;
810     RegexObj := TRegExpr.Create;
811     try
812     RegexObj.ModifierG := false; {turn off greedy matches}
813 tony 229 RegexObj.ModifierI := true; {case insensitive}
814 tony 209 RegexObj.Expression := '^ *CONNECT +''(.*)''';
815 tony 229 if RegexObj.Exec(stmt) then
816 tony 209 begin
817     FDatabase.DatabaseName := system.copy(stmt,RegexObj.MatchPos[1],RegexObj.MatchLen[1]);
818     end;
819    
820     RegexObj.Expression := ' +ROLE +''(.+)''';
821 tony 229 if RegexObj.Exec(stmt) then
822 tony 209 FDatabase.Params.Values['sql_role_name'] := RegexObj.Match[1]
823     else
824     with FDatabase.Params do
825     if IndexOfName('sql_role_name') <> -1 then
826     Delete(IndexOfName('sql_role_name'));
827    
828     RegexObj.Expression := ' +CACHE +([0-9]+)';
829 tony 229 if RegexObj.Exec(stmt) then
830 tony 209 FDatabase.Params.Values['cache_manager'] := RegexObj.Match[1]
831     else
832     with FDatabase.Params do
833     if IndexOfName('cache_manager') <> -1 then
834     Delete(IndexOfName('cache_manager'));
835     finally
836     RegexObj.Free;
837     end;
838     end;
839    
840     procedure UpdateUserPassword;
841     var RegexObj: TRegExpr;
842     begin
843     RegexObj := TRegExpr.Create;
844     try
845     RegexObj.ModifierG := false; {turn off greedy matches}
846 tony 229 RegexObj.ModifierI := true; {case insensitive}
847 tony 209 RegexObj.Expression := '^ *CREATE +(DATABASE|SCHEMA) +(''.*'') +USER +''(.+)''';
848 tony 229 if not RegexObj.Exec(stmt) and (FDatabase.Params.IndexOfName('user_name') <> -1) then
849 tony 209 begin
850     RegexObj.Expression := '^ *CREATE +(DATABASE|SCHEMA) +(''.*'')';
851 tony 229 if RegexObj.Exec(stmt) then
852 tony 209 begin
853     system.Insert(' USER ''' + FDatabase.Params.Values['user_name'] +'''',stmt,
854     RegexObj.MatchPos[2] + RegexObj.MatchLen[2]);
855     end;
856     end;
857    
858     RegexObj.Expression := '^ *CREATE +(DATABASE|SCHEMA) +''.*'' +USER +''.+'' PASSWORD +''(.+)''';
859 tony 229 if not RegexObj.Exec(stmt) and (FDatabase.Params.IndexOfName('password') <> -1) then
860 tony 209 begin
861     RegexObj.Expression := '^ *CREATE +(DATABASE|SCHEMA) +''.*'' +(USER +''.+'')';
862 tony 229 if RegexObj.Exec(stmt) then
863 tony 209 begin
864     system.Insert(' PASSWORD ''' + FDatabase.Params.Values['password'] +'''',stmt,
865     RegexObj.MatchPos[2] + RegexObj.MatchLen[2]);
866     end;
867     end;
868     finally
869     RegexObj.Free;
870     end;
871     end;
872    
873     var RegexObj: TRegExpr;
874     n: integer;
875     charsetid: integer;
876     param: string;
877     Terminator: char;
878     FileName: string;
879     DBConnected: boolean;
880     LoginPrompt: boolean;
881     begin
882     Result := false;
883 tony 263 Terminator := FSQLReader.Terminator;
884 tony 209 RegexObj := TRegExpr.Create;
885     try
886     {process create database}
887 tony 229 RegexObj.ModifierI := true; {case insensitive}
888 tony 209 RegexObj.Expression := '^ *CREATE +(DATABASE|SCHEMA) +''(.*)''(.*)(\' + Terminator + '|)';
889 tony 229 if RegexObj.Exec(stmt) then
890 tony 209 begin
891     if IgnoreCreateDatabase then
892     begin
893     Result := true;
894     Exit;
895     end;
896     FileName := system.copy(stmt,RegexObj.MatchPos[2], RegexObj.MatchLen[2]);
897     if assigned(FOnCreateDatabase) then
898     OnCreateDatabase(self,FileName);
899     stmt := 'CREATE DATABASE ''' + FileName + '''' + system.copy(stmt,RegexObj.MatchPos[3], RegexObj.MatchLen[3]);
900     UpdateUserPassword;
901 tony 272 if FDatabase.Connected then
902     FDatabase.Dropdatabase;
903 tony 209 FDatabase.CreateDatabase(stmt);
904     Result := true;
905     Exit;
906     end;
907    
908     {process connect statement}
909     RegexObj.Expression := '^ *CONNECT +.*(\' + Terminator + '|)';
910 tony 229 if RegexObj.Exec(stmt) then
911 tony 209 begin
912     ExtractConnectInfo;
913 tony 229 FDatabase.Connected := false;
914     FDatabase.Connected := true;
915 tony 209 Result := true;
916     Exit;
917     end;
918    
919     {Process Drop Database}
920     RegexObj.Expression := '^ *DROP +DATABASE *(\' + Terminator + '|)';
921 tony 229 if RegexObj.Exec(stmt) then
922 tony 209 begin
923     FDatabase.DropDatabase;
924     Result := true;
925     Exit;
926     end;
927    
928     {process commit statement}
929     RegexObj.Expression := '^ *COMMIT *(\' + Terminator + '|)';
930 tony 229 if RegexObj.Exec(stmt) then
931 tony 209 begin
932     DoCommit;
933     Result := true;
934     Exit;
935     end;
936    
937     {process Reconnect statement}
938     RegexObj.Expression := '^ *RECONNECT *(\' + Terminator + '|)';
939 tony 229 if RegexObj.Exec(stmt) then
940 tony 209 begin
941     DoReconnect;
942     Result := true;
943     Exit;
944     end;
945    
946    
947     {Process Set Term}
948     RegexObj.Expression := '^ *SET +TERM +(.) *(\' + Terminator + '|)';
949 tony 229 if RegexObj.Exec(stmt) then
950 tony 209 begin
951 tony 263 FSQLReader.Terminator := RegexObj.Match[1][1];
952 tony 209 Result := true;
953     Exit;
954     end;
955    
956     {process Set SQL Dialect}
957     RegexObj.Expression := '^ *SET +SQL +DIALECT +([0-9]) *(\' + Terminator + '|)';
958 tony 229 if RegexObj.Exec(stmt) then
959 tony 209 begin
960     n := StrToInt(RegexObj.Match[1]);
961     if Database.SQLDialect <> n then
962     begin
963     Database.SQLDialect := n;
964     if Database.Connected then
965     DoReconnect;
966     end;
967     Result := true;
968     Exit;
969     end;
970    
971     {Process Remaining Set statements}
972     RegexObj.Expression := '^ *SET +([A-Z]+)( +[A-Z0-9]+|) *(\' + Terminator + '|)';
973 tony 229 if RegexObj.Exec(stmt) then
974 tony 209 begin
975     command := AnsiUpperCase(RegexObj.Match[1]);
976     param := trim(RegexObj.Match[2]);
977 tony 287 if command = 'GENERATOR' then
978     begin
979     Result := false;
980     Exit;
981     end;
982 tony 209 if command = 'AUTODDL' then
983     AutoDDL := ((RegexObj.MatchLen[2] = 0) and not AutoDDL) or
984     (RegexObj.MatchLen[2] > 0) and Toggle(param)
985     else
986     if command = 'BAIL' then
987     StopOnFirstError := ((RegexObj.MatchLen[2] = 0) and not StopOnFirstError) or
988     (RegexObj.MatchLen[2] > 0) and Toggle(param)
989     else
990     if command = 'ECHO' then
991     Echo := ((RegexObj.MatchLen[2] = 0) and not Echo) or
992     (RegexObj.MatchLen[2] > 0) and Toggle(param)
993     else
994     if command = 'COUNT' then
995     ShowAffectedRows := ((RegexObj.MatchLen[2] = 0) and not ShowAffectedRows) or
996     (RegexObj.MatchLen[2] > 0) and Toggle(param)
997     else
998     if command = 'STATS' then
999     ShowPerformanceStats := ((RegexObj.MatchLen[2] = 0) and not FShowPerformanceStats) or
1000     (RegexObj.MatchLen[2] > 0) and Toggle(param)
1001     else
1002     if command = 'NAMES' then
1003     begin
1004     if Database.Attachment.CharSetName2CharSetID(param,charsetid) then
1005     begin
1006     DBConnected := Database.Connected;
1007     LoginPrompt := Database.LoginPrompt;
1008     Database.LoginPrompt := false;
1009     Database.Connected := false;
1010     Database.Params.Values['lc_ctype'] := param;
1011     Database.Connected := DBConnected;
1012     Database.LoginPrompt := LoginPrompt;
1013     end
1014     else
1015     raise Exception.CreateFmt(sInvalidCharacterSet, [param,stmt]);
1016     end
1017     else
1018     begin
1019     if assigned(DataOutputFormatter) then
1020     DataOutputFormatter.SetCommand(command,param,stmt,Result);
1021 tony 263 if not Result then
1022     begin
1023     if assigned(OnSetStatement) then
1024     OnSetStatement(self,command,param,stmt,Result)
1025     else
1026     raise Exception.CreateFmt(sInvalidSetStatement, [command,stmt]);
1027     end;
1028 tony 209 Exit;
1029     end;
1030     Result := true;
1031     Exit;
1032     end;
1033    
1034     finally
1035     RegexObj.Free;
1036     end;
1037     end;
1038    
1039     procedure TCustomIBXScript.SetTransaction(AValue: TIBTransaction);
1040     begin
1041     if FTransaction = AValue then Exit;
1042     FTransaction := AValue;
1043     end;
1044    
1045     constructor TCustomIBXScript.Create(aOwner: TComponent);
1046     begin
1047     inherited Create(aOwner);
1048     FStopOnFirstError := true;
1049     FEcho := true;
1050     FAutoDDL := true;
1051     FISQL := TIBSQL.Create(self);
1052     FISQL.ParamCheck := true;
1053     FInternalTransaction := TIBTransaction.Create(self);
1054     FInternalTransaction.Params.Clear;
1055     FInternalTransaction.Params.Add('concurrency');
1056     FInternalTransaction.Params.Add('wait');
1057     end;
1058    
1059     destructor TCustomIBXScript.Destroy;
1060     begin
1061 tony 263 if FSQLReader <> nil then FSQLReader.Free;
1062 tony 209 if FISQL <> nil then FISQL.Free;
1063     if FInternalTransaction <> nil then FInternalTransaction.Free;
1064     inherited Destroy;
1065     end;
1066    
1067     procedure TCustomIBXScript.DefaultSelectSQLHandler(aSQLText: string);
1068     begin
1069     if assigned(DataOutputFormatter) then
1070     DataOutputFormatter.DataOut(aSQLText,@Add2Log)
1071     else
1072 tony 263 FSQLReader.ShowError(sNoSelectSQL);
1073 tony 209 end;
1074    
1075 tony 263 { TInteractiveSQLStatementReader }
1076 tony 209
1077 tony 402 function TInteractiveSQLStatementReader.GetErrorPrefix: AnsiString;
1078 tony 209 begin
1079 tony 263 Result := '';
1080 tony 209 end;
1081    
1082 tony 263 function TInteractiveSQLStatementReader.GetNextLine(var Line: string): boolean;
1083 tony 209 begin
1084 tony 263 if FNextStatement then
1085     write(FPrompt)
1086     else
1087     write(FContinuePrompt);
1088     Result := not system.EOF;
1089     if Result then
1090 tony 209 begin
1091 tony 263 readln(Line);
1092     EchoNextLine(Line);
1093 tony 209 end;
1094     end;
1095    
1096 tony 402 function TInteractiveSQLStatementReader.GetChar: AnsiChar;
1097 tony 209 begin
1098 tony 263 if Terminated then
1099     Result := #0
1100     else
1101     if FLineIndex > Length(FLine) then
1102 tony 209 begin
1103 tony 263 Result := LF;
1104     FLineIndex := 0;
1105     end
1106     else
1107     if FLineIndex = 0 then
1108 tony 209 begin
1109 tony 263 if not GetNextLine(FLine) then
1110     Result := #0
1111 tony 209 else
1112 tony 263 if Length(FLine) = 0 then
1113     Result := LF
1114 tony 209 else
1115     begin
1116 tony 263 Result := FLine[1];
1117     FLineIndex := 2;
1118     end
1119     end
1120     else
1121     begin
1122     Result := FLine[FLineIndex];
1123     Inc(FLineIndex);
1124 tony 209 end;
1125     end;
1126    
1127 tony 263 constructor TInteractiveSQLStatementReader.Create(aPrompt: string; aContinue: string);
1128 tony 209 begin
1129     inherited Create;
1130 tony 263 FPrompt := aPrompt;
1131     FLineIndex := 0;
1132     FNextStatement := true;
1133     FContinuePrompt := aContinue;
1134 tony 209 end;
1135    
1136 tony 263 function TInteractiveSQLStatementReader.GetNextStatement(var stmt: string
1137     ): boolean;
1138 tony 209 begin
1139 tony 263 Result := inherited GetNextStatement(stmt);
1140     FNextStatement := Result;
1141 tony 209 end;
1142    
1143 tony 263 { TBatchSQLStatementReader }
1144    
1145 tony 402 function TBatchSQLStatementReader.GetChar: AnsiChar;
1146 tony 209 begin
1147 tony 263 if not EOF and assigned(FInStream) and not (FInStream.Position = FInStream.Size) then
1148 tony 209 begin
1149 tony 263 Result := char(FInStream.ReadByte);
1150     if Result = LF then
1151 tony 209 begin
1152 tony 263 EchoNextLine(FCurLine);
1153     FCurLine := '';
1154     if assigned(OnProgressEvent) then
1155     OnProgressEvent(self,false,FIndex+1);
1156     Inc(FLineIndex);
1157     FIndex := 1;
1158     end
1159     else
1160 tony 353 if Result <> CR then
1161 tony 209 begin
1162 tony 263 FCurLine += Result;
1163     Inc(FIndex);
1164 tony 209 end;
1165 tony 263 end
1166 tony 209 else
1167 tony 263 Result := #0;
1168 tony 209 end;
1169    
1170 tony 402 function TBatchSQLStatementReader.GetErrorPrefix: AnsiString;
1171 tony 209 begin
1172     Result := Format(sOnLineError,[FLineIndex,FIndex]);
1173     end;
1174    
1175 tony 263 procedure TBatchSQLStatementReader.Reset;
1176 tony 209 begin
1177 tony 263 inherited Reset;
1178     if FOwnsInStream and assigned(FInStream) then
1179     FInStream.Free;
1180     FInStream := nil;
1181     FOwnsInStream := false;
1182     FLineIndex := 1;
1183     FIndex := 1;
1184 tony 315 FCurLine := '';
1185 tony 209 end;
1186    
1187 tony 263 procedure TBatchSQLStatementReader.SetStreamSource(Lines: TStrings);
1188 tony 209 begin
1189 tony 263 Reset;
1190     FInStream := TMemoryStream.Create;
1191     FOwnsInStream := true;
1192     Lines.SaveToStream(FInStream);
1193     FInStream.Position := 0;
1194     if assigned(OnProgressEvent) then
1195     OnProgressEvent(self,true,FInStream.Size);
1196 tony 209 end;
1197    
1198 tony 263 procedure TBatchSQLStatementReader.SetStreamSource(S: TStream);
1199 tony 209 begin
1200 tony 263 Reset;
1201     FInStream := S;
1202 tony 209 if assigned(OnProgressEvent) then
1203 tony 263 OnProgressEvent(self,true,S.Size - S.Position);
1204 tony 209 end;
1205    
1206 tony 263 procedure TBatchSQLStatementReader.SetStreamSource(FileName: string);
1207 tony 209 begin
1208 tony 263 Reset;
1209     FInStream := TFileStream.Create(FileName,fmShareCompat);
1210     FOwnsInStream := true;
1211 tony 209 if assigned(OnProgressEvent) then
1212 tony 263 OnProgressEvent(self,true,FInStream.Size);
1213 tony 209 end;
1214    
1215 tony 263 procedure TBatchSQLStatementReader.SetStringStreamSource(S: string);
1216 tony 209 begin
1217 tony 263 Reset;
1218     FInStream := TStringStream.Create(S);
1219     FOwnsInStream := true;
1220 tony 209 if assigned(OnProgressEvent) then
1221 tony 263 OnProgressEvent(self,true,FInStream.Size);
1222 tony 209 end;
1223    
1224     end.
1225    

Properties

Name Value
svn:eol-style native