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

# Content
1 (*
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 uses Classes, IBDatabase, IBSQL, IB, IBDataOutput, IBUtils;
35
36 type
37
38 TOnNextLine = procedure(Sender: TObject; Line: string) of object;
39
40 { TSQLStatementReader }
41
42 TSQLStatementReader = class(TSQLXMLReader)
43 private
44 type
45 TSQLState = (stDefault, stInStmt, stInBlock, stInArrayDim, stInDeclare);
46 private
47 FDatabase: TIBDatabase;
48 FHasBegin: boolean;
49 FOnNextLine: TOnNextLine;
50 FTerminator: char;
51 FTransaction: TIBTransaction;
52 protected
53 procedure EchoNextLine(aLine: string);
54 function GetAttachment: IAttachment; override;
55 function GetTransaction: ITransaction; override;
56 public
57 constructor Create;
58 function GetNextStatement(var stmt: string) : boolean; virtual;
59 property HasBegin: boolean read FHasBegin;
60 property Terminator: char read FTerminator write FTerminator default DefaultTerminator;
61 property OnNextLine: TOnNextLine read FOnNextLine write FOnNextLine;
62 property Database: TIBDatabase read FDatabase write FDatabase;
63 property Transaction: TIBTransaction read FTransaction write FTransaction;
64 end;
65
66
67 { TBatchSQLStatementReader }
68
69 {This SQL Reader supports non-interactive parsing of a text file, stream or
70 lines of text.}
71
72 TBatchSQLStatementReader = class(TSQLStatementReader)
73 private
74 FInStream: TStream;
75 FOwnsInStream: boolean;
76 FLineIndex: integer;
77 FIndex: integer;
78 FCurLine: string;
79 protected
80 function GetChar: AnsiChar; override;
81 function GetErrorPrefix: AnsiString; override;
82 public
83 procedure Reset; override;
84 procedure SetStreamSource(Lines: TStrings); overload;
85 procedure SetStreamSource(S: TStream); overload;
86 procedure SetStreamSource(FileName: string); overload;
87 procedure SetStringStreamSource(S: string);
88 end;
89
90 { TInteractiveSQLStatementReader }
91
92 {This SQL reader supports interactive parsing of commands and
93 SQL statements entered at a console}
94
95 TInteractiveSQLStatementReader = class(TSQLStatementReader)
96 private
97 FPrompt: string;
98 FContinuePrompt: string;
99 FTerminated: boolean;
100 FLine: string;
101 FLineIndex: integer;
102 FNextStatement: boolean;
103 function GetNextLine(var Line: string):boolean;
104 protected
105 function GetChar: AnsiChar; override;
106 function GetErrorPrefix: AnsiString; override;
107 public
108 constructor Create(aPrompt: string='SQL>'; aContinue: string = 'CON>');
109 function GetNextStatement(var stmt: string) : boolean; override;
110 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 FSQLReader: TSQLStatementReader;
135 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 procedure ExecSQL(stmt: string);
165 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 procedure SetSQLStatementReader(SQLStatementReader: TSQLStatementReader);
170 public
171 constructor Create(aOwner: TComponent); override;
172 destructor Destroy; override;
173 procedure DefaultSelectSQLHandler(aSQLText: string);
174 property SQLStatementReader: TSQLStatementReader read FSQLReader;
175 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 sInvalidCharacterSet = 'Unrecognised character set name - "%s"';
285 sOnLineError = 'On Line %d Character %d: ';
286
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 { TSQLStatementReader }
298
299 procedure TSQLStatementReader.EchoNextLine(aLine: string);
300 begin
301 if assigned(FOnNextLine) then
302 OnNextLine(self,aLine);
303 end;
304
305 function TSQLStatementReader.GetAttachment: IAttachment;
306 begin
307 if FDatabase <> nil then
308 Result := FDatabase.Attachment
309 else
310 Result := nil;
311 end;
312
313 function TSQLStatementReader.GetTransaction: ITransaction;
314 begin
315 if FTransaction <> nil then
316 Result := FTransaction.TransactionIntf
317 else
318 Result := nil;
319 end;
320
321 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 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 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 sqltQuotedString:
496 stmt += '''' + SQLSafeString(TokenText) + ''''; {exists some DECLARE with cursor having SELECT ...\... rc.rdb$constraint_type = 'PRIMARY KEY');}
497
498 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 // writeln(stmt);
513 end;
514 Result := stmt <> '';
515 end;
516
517
518
519 { TIBXScript }
520
521 constructor TIBXScript.Create(aOwner: TComponent);
522 begin
523 inherited Create(aOwner);
524 SetSQLStatementReader(TBatchSQLStatementReader.Create);
525 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 TBatchSQLStatementReader(FSQLReader).SetStreamSource(SQLFile);
543 Result := ProcessStream;
544 end;
545
546 function TIBXScript.RunScript(SQLStream: TStream): boolean;
547 begin
548 TBatchSQLStatementReader(FSQLReader).SetStreamSource(SQLStream);
549 Result := ProcessStream;
550 end;
551
552 function TIBXScript.RunScript(SQLLines: TStrings): boolean;
553 begin
554 TBatchSQLStatementReader(FSQLReader).SetStreamSource(SQLLines);
555 Result := ProcessStream;
556 end;
557
558 function TIBXScript.ExecSQLScript(sql: string): boolean;
559 begin
560 TBatchSQLStatementReader(FSQLReader).SetStringStreamSource(sql);
561 Result := ProcessStream;
562 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 Database.Reconnect;
587 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 // FISQL.ParamCheck := not FSQLReader.HasBegin; {Probably PSQL}
598 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 Result := FSQLReader.OnProgressEvent;
637 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 FSQLReader.OnProgressEvent := AValue;
686 end;
687
688 procedure TCustomIBXScript.SetParamValue(SQLVar: ISQLParam);
689 var BlobID: TISC_QUAD;
690 ix: integer;
691 begin
692 if (SQLVar.SQLType = SQL_BLOB) and (Pos(TSQLXMLReader.ibx_blob,SQLVar.Name) = 1) then
693 begin
694 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 Exit;
697 end
698 else
699 if (SQLVar.SQLType = SQL_ARRAY) and (Pos(TSQLXMLReader.ibx_array,SQLVar.Name) = 1) then
700 begin
701 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 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 FSQLReader.Database := Database;
732 if FTransaction = nil then
733 FSQLReader.Transaction := FInternalTransaction
734 else
735 FSQLReader.Transaction := FTransaction;
736 while FSQLReader.GetNextStatement(stmt) do
737 try
738 stmt := trim(stmt);
739 // writeln('stmt = "',stmt,'"');
740 if stmt = '' then continue;
741 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 FSQLReader.Terminator := TSQLStatementReader.DefaultTerminator;
749 if assigned(OnErrorLog) then
750 begin
751 Add2Log(Format(sStatementError,[FSQLReader.GetErrorPrefix,
752 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 procedure TCustomIBXScript.SetSQLStatementReader(
763 SQLStatementReader: TSQLStatementReader);
764 begin
765 FSQLReader := SQLStatementReader;
766 FSQLReader.OnNextLine := @EchoNextLine;
767 end;
768
769 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 if RegexObj.Exec(stmt) then
792 FDatabase.Params.Values['user_name'] := RegexObj.Match[1];
793
794 RegexObj.Expression := ' +PASSWORD +''(.+)''';
795 if RegexObj.Exec(stmt) then
796 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 RegexObj.ModifierI := true; {case insensitive}
811 RegexObj.Expression := '^ *CONNECT +''(.*)''';
812 if RegexObj.Exec(stmt) then
813 begin
814 FDatabase.DatabaseName := system.copy(stmt,RegexObj.MatchPos[1],RegexObj.MatchLen[1]);
815 end;
816
817 RegexObj.Expression := ' +ROLE +''(.+)''';
818 if RegexObj.Exec(stmt) then
819 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 if RegexObj.Exec(stmt) then
827 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 RegexObj.ModifierI := true; {case insensitive}
844 RegexObj.Expression := '^ *CREATE +(DATABASE|SCHEMA) +(''.*'') +USER +''(.+)''';
845 if not RegexObj.Exec(stmt) and (FDatabase.Params.IndexOfName('user_name') <> -1) then
846 begin
847 RegexObj.Expression := '^ *CREATE +(DATABASE|SCHEMA) +(''.*'')';
848 if RegexObj.Exec(stmt) then
849 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 if not RegexObj.Exec(stmt) and (FDatabase.Params.IndexOfName('password') <> -1) then
857 begin
858 RegexObj.Expression := '^ *CREATE +(DATABASE|SCHEMA) +''.*'' +(USER +''.+'')';
859 if RegexObj.Exec(stmt) then
860 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 Terminator := FSQLReader.Terminator;
881 RegexObj := TRegExpr.Create;
882 try
883 {process create database}
884 RegexObj.ModifierI := true; {case insensitive}
885 RegexObj.Expression := '^ *CREATE +(DATABASE|SCHEMA) +''(.*)''(.*)(\' + Terminator + '|)';
886 if RegexObj.Exec(stmt) then
887 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 if FDatabase.Connected then
899 FDatabase.Dropdatabase;
900 FDatabase.CreateDatabase(stmt);
901 Result := true;
902 Exit;
903 end;
904
905 {process connect statement}
906 RegexObj.Expression := '^ *CONNECT +.*(\' + Terminator + '|)';
907 if RegexObj.Exec(stmt) then
908 begin
909 ExtractConnectInfo;
910 FDatabase.Connected := false;
911 FDatabase.Connected := true;
912 Result := true;
913 Exit;
914 end;
915
916 {Process Drop Database}
917 RegexObj.Expression := '^ *DROP +DATABASE *(\' + Terminator + '|)';
918 if RegexObj.Exec(stmt) then
919 begin
920 FDatabase.DropDatabase;
921 Result := true;
922 Exit;
923 end;
924
925 {process commit statement}
926 RegexObj.Expression := '^ *COMMIT *(\' + Terminator + '|)';
927 if RegexObj.Exec(stmt) then
928 begin
929 DoCommit;
930 Result := true;
931 Exit;
932 end;
933
934 {process Reconnect statement}
935 RegexObj.Expression := '^ *RECONNECT *(\' + Terminator + '|)';
936 if RegexObj.Exec(stmt) then
937 begin
938 DoReconnect;
939 Result := true;
940 Exit;
941 end;
942
943
944 {Process Set Term}
945 RegexObj.Expression := '^ *SET +TERM +(.) *(\' + Terminator + '|)';
946 if RegexObj.Exec(stmt) then
947 begin
948 FSQLReader.Terminator := RegexObj.Match[1][1];
949 Result := true;
950 Exit;
951 end;
952
953 {process Set SQL Dialect}
954 RegexObj.Expression := '^ *SET +SQL +DIALECT +([0-9]) *(\' + Terminator + '|)';
955 if RegexObj.Exec(stmt) then
956 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 if RegexObj.Exec(stmt) then
971 begin
972 command := AnsiUpperCase(RegexObj.Match[1]);
973 param := trim(RegexObj.Match[2]);
974 if command = 'GENERATOR' then
975 begin
976 Result := false;
977 Exit;
978 end;
979 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 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 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 if FSQLReader <> nil then FSQLReader.Free;
1059 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 FSQLReader.ShowError(sNoSelectSQL);
1070 end;
1071
1072 { TInteractiveSQLStatementReader }
1073
1074 function TInteractiveSQLStatementReader.GetErrorPrefix: AnsiString;
1075 begin
1076 Result := '';
1077 end;
1078
1079 function TInteractiveSQLStatementReader.GetNextLine(var Line: string): boolean;
1080 begin
1081 if FNextStatement then
1082 write(FPrompt)
1083 else
1084 write(FContinuePrompt);
1085 Result := not system.EOF;
1086 if Result then
1087 begin
1088 readln(Line);
1089 EchoNextLine(Line);
1090 end;
1091 end;
1092
1093 function TInteractiveSQLStatementReader.GetChar: AnsiChar;
1094 begin
1095 if Terminated then
1096 Result := #0
1097 else
1098 if FLineIndex > Length(FLine) then
1099 begin
1100 Result := LF;
1101 FLineIndex := 0;
1102 end
1103 else
1104 if FLineIndex = 0 then
1105 begin
1106 if not GetNextLine(FLine) then
1107 Result := #0
1108 else
1109 if Length(FLine) = 0 then
1110 Result := LF
1111 else
1112 begin
1113 Result := FLine[1];
1114 FLineIndex := 2;
1115 end
1116 end
1117 else
1118 begin
1119 Result := FLine[FLineIndex];
1120 Inc(FLineIndex);
1121 end;
1122 end;
1123
1124 constructor TInteractiveSQLStatementReader.Create(aPrompt: string; aContinue: string);
1125 begin
1126 inherited Create;
1127 FPrompt := aPrompt;
1128 FLineIndex := 0;
1129 FNextStatement := true;
1130 FContinuePrompt := aContinue;
1131 end;
1132
1133 function TInteractiveSQLStatementReader.GetNextStatement(var stmt: string
1134 ): boolean;
1135 begin
1136 Result := inherited GetNextStatement(stmt);
1137 FNextStatement := Result;
1138 end;
1139
1140 { TBatchSQLStatementReader }
1141
1142 function TBatchSQLStatementReader.GetChar: AnsiChar;
1143 begin
1144 if not EOF and assigned(FInStream) and not (FInStream.Position = FInStream.Size) then
1145 begin
1146 Result := char(FInStream.ReadByte);
1147 if Result = LF then
1148 begin
1149 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 if Result <> CR then
1158 begin
1159 FCurLine += Result;
1160 Inc(FIndex);
1161 end;
1162 end
1163 else
1164 Result := #0;
1165 end;
1166
1167 function TBatchSQLStatementReader.GetErrorPrefix: AnsiString;
1168 begin
1169 Result := Format(sOnLineError,[FLineIndex,FIndex]);
1170 end;
1171
1172 procedure TBatchSQLStatementReader.Reset;
1173 begin
1174 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 FCurLine := '';
1182 end;
1183
1184 procedure TBatchSQLStatementReader.SetStreamSource(Lines: TStrings);
1185 begin
1186 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 end;
1194
1195 procedure TBatchSQLStatementReader.SetStreamSource(S: TStream);
1196 begin
1197 Reset;
1198 FInStream := S;
1199 if assigned(OnProgressEvent) then
1200 OnProgressEvent(self,true,S.Size - S.Position);
1201 end;
1202
1203 procedure TBatchSQLStatementReader.SetStreamSource(FileName: string);
1204 begin
1205 Reset;
1206 FInStream := TFileStream.Create(FileName,fmShareCompat);
1207 FOwnsInStream := true;
1208 if assigned(OnProgressEvent) then
1209 OnProgressEvent(self,true,FInStream.Size);
1210 end;
1211
1212 procedure TBatchSQLStatementReader.SetStringStreamSource(S: string);
1213 begin
1214 Reset;
1215 FInStream := TStringStream.Create(S);
1216 FOwnsInStream := true;
1217 if assigned(OnProgressEvent) then
1218 OnProgressEvent(self,true,FInStream.Size);
1219 end;
1220
1221 end.
1222

Properties

Name Value
svn:eol-style native