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

# 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 sqltIdentifierInDoubleQuotes:
499 stmt += '"' + TokenText + '"';
500
501 sqltSemiColon:
502 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 // writeln(stmt);
516 end;
517 Result := stmt <> '';
518 end;
519
520
521
522 { TIBXScript }
523
524 constructor TIBXScript.Create(aOwner: TComponent);
525 begin
526 inherited Create(aOwner);
527 SetSQLStatementReader(TBatchSQLStatementReader.Create);
528 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 TBatchSQLStatementReader(FSQLReader).SetStreamSource(SQLFile);
546 Result := ProcessStream;
547 end;
548
549 function TIBXScript.RunScript(SQLStream: TStream): boolean;
550 begin
551 TBatchSQLStatementReader(FSQLReader).SetStreamSource(SQLStream);
552 Result := ProcessStream;
553 end;
554
555 function TIBXScript.RunScript(SQLLines: TStrings): boolean;
556 begin
557 TBatchSQLStatementReader(FSQLReader).SetStreamSource(SQLLines);
558 Result := ProcessStream;
559 end;
560
561 function TIBXScript.ExecSQLScript(sql: string): boolean;
562 begin
563 TBatchSQLStatementReader(FSQLReader).SetStringStreamSource(sql);
564 Result := ProcessStream;
565 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 Database.Reconnect;
590 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 // FISQL.ParamCheck := not FSQLReader.HasBegin; {Probably PSQL}
601 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 Result := FSQLReader.OnProgressEvent;
640 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 FSQLReader.OnProgressEvent := AValue;
689 end;
690
691 procedure TCustomIBXScript.SetParamValue(SQLVar: ISQLParam);
692 var BlobID: TISC_QUAD;
693 ix: integer;
694 begin
695 if (SQLVar.SQLType = SQL_BLOB) and (Pos(TSQLXMLReader.ibx_blob,SQLVar.Name) = 1) then
696 begin
697 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 Exit;
700 end
701 else
702 if (SQLVar.SQLType = SQL_ARRAY) and (Pos(TSQLXMLReader.ibx_array,SQLVar.Name) = 1) then
703 begin
704 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 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 FSQLReader.Database := Database;
735 if FTransaction = nil then
736 FSQLReader.Transaction := FInternalTransaction
737 else
738 FSQLReader.Transaction := FTransaction;
739 while FSQLReader.GetNextStatement(stmt) do
740 try
741 stmt := trim(stmt);
742 // writeln('stmt = "',stmt,'"');
743 if stmt = '' then continue;
744 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 FSQLReader.Terminator := TSQLStatementReader.DefaultTerminator;
752 if assigned(OnErrorLog) then
753 begin
754 Add2Log(Format(sStatementError,[FSQLReader.GetErrorPrefix,
755 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 procedure TCustomIBXScript.SetSQLStatementReader(
766 SQLStatementReader: TSQLStatementReader);
767 begin
768 FSQLReader := SQLStatementReader;
769 FSQLReader.OnNextLine := @EchoNextLine;
770 end;
771
772 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 if RegexObj.Exec(stmt) then
795 FDatabase.Params.Values['user_name'] := RegexObj.Match[1];
796
797 RegexObj.Expression := ' +PASSWORD +''(.+)''';
798 if RegexObj.Exec(stmt) then
799 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 RegexObj.ModifierI := true; {case insensitive}
814 RegexObj.Expression := '^ *CONNECT +''(.*)''';
815 if RegexObj.Exec(stmt) then
816 begin
817 FDatabase.DatabaseName := system.copy(stmt,RegexObj.MatchPos[1],RegexObj.MatchLen[1]);
818 end;
819
820 RegexObj.Expression := ' +ROLE +''(.+)''';
821 if RegexObj.Exec(stmt) then
822 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 if RegexObj.Exec(stmt) then
830 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 RegexObj.ModifierI := true; {case insensitive}
847 RegexObj.Expression := '^ *CREATE +(DATABASE|SCHEMA) +(''.*'') +USER +''(.+)''';
848 if not RegexObj.Exec(stmt) and (FDatabase.Params.IndexOfName('user_name') <> -1) then
849 begin
850 RegexObj.Expression := '^ *CREATE +(DATABASE|SCHEMA) +(''.*'')';
851 if RegexObj.Exec(stmt) then
852 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 if not RegexObj.Exec(stmt) and (FDatabase.Params.IndexOfName('password') <> -1) then
860 begin
861 RegexObj.Expression := '^ *CREATE +(DATABASE|SCHEMA) +''.*'' +(USER +''.+'')';
862 if RegexObj.Exec(stmt) then
863 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 Terminator := FSQLReader.Terminator;
884 RegexObj := TRegExpr.Create;
885 try
886 {process create database}
887 RegexObj.ModifierI := true; {case insensitive}
888 RegexObj.Expression := '^ *CREATE +(DATABASE|SCHEMA) +''(.*)''(.*)(\' + Terminator + '|)';
889 if RegexObj.Exec(stmt) then
890 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 if FDatabase.Connected then
902 FDatabase.Dropdatabase;
903 FDatabase.CreateDatabase(stmt);
904 Result := true;
905 Exit;
906 end;
907
908 {process connect statement}
909 RegexObj.Expression := '^ *CONNECT +.*(\' + Terminator + '|)';
910 if RegexObj.Exec(stmt) then
911 begin
912 ExtractConnectInfo;
913 FDatabase.Connected := false;
914 FDatabase.Connected := true;
915 Result := true;
916 Exit;
917 end;
918
919 {Process Drop Database}
920 RegexObj.Expression := '^ *DROP +DATABASE *(\' + Terminator + '|)';
921 if RegexObj.Exec(stmt) then
922 begin
923 FDatabase.DropDatabase;
924 Result := true;
925 Exit;
926 end;
927
928 {process commit statement}
929 RegexObj.Expression := '^ *COMMIT *(\' + Terminator + '|)';
930 if RegexObj.Exec(stmt) then
931 begin
932 DoCommit;
933 Result := true;
934 Exit;
935 end;
936
937 {process Reconnect statement}
938 RegexObj.Expression := '^ *RECONNECT *(\' + Terminator + '|)';
939 if RegexObj.Exec(stmt) then
940 begin
941 DoReconnect;
942 Result := true;
943 Exit;
944 end;
945
946
947 {Process Set Term}
948 RegexObj.Expression := '^ *SET +TERM +(.) *(\' + Terminator + '|)';
949 if RegexObj.Exec(stmt) then
950 begin
951 FSQLReader.Terminator := RegexObj.Match[1][1];
952 Result := true;
953 Exit;
954 end;
955
956 {process Set SQL Dialect}
957 RegexObj.Expression := '^ *SET +SQL +DIALECT +([0-9]) *(\' + Terminator + '|)';
958 if RegexObj.Exec(stmt) then
959 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 if RegexObj.Exec(stmt) then
974 begin
975 command := AnsiUpperCase(RegexObj.Match[1]);
976 param := trim(RegexObj.Match[2]);
977 if command = 'GENERATOR' then
978 begin
979 Result := false;
980 Exit;
981 end;
982 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 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 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 if FSQLReader <> nil then FSQLReader.Free;
1062 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 FSQLReader.ShowError(sNoSelectSQL);
1073 end;
1074
1075 { TInteractiveSQLStatementReader }
1076
1077 function TInteractiveSQLStatementReader.GetErrorPrefix: AnsiString;
1078 begin
1079 Result := '';
1080 end;
1081
1082 function TInteractiveSQLStatementReader.GetNextLine(var Line: string): boolean;
1083 begin
1084 if FNextStatement then
1085 write(FPrompt)
1086 else
1087 write(FContinuePrompt);
1088 Result := not system.EOF;
1089 if Result then
1090 begin
1091 readln(Line);
1092 EchoNextLine(Line);
1093 end;
1094 end;
1095
1096 function TInteractiveSQLStatementReader.GetChar: AnsiChar;
1097 begin
1098 if Terminated then
1099 Result := #0
1100 else
1101 if FLineIndex > Length(FLine) then
1102 begin
1103 Result := LF;
1104 FLineIndex := 0;
1105 end
1106 else
1107 if FLineIndex = 0 then
1108 begin
1109 if not GetNextLine(FLine) then
1110 Result := #0
1111 else
1112 if Length(FLine) = 0 then
1113 Result := LF
1114 else
1115 begin
1116 Result := FLine[1];
1117 FLineIndex := 2;
1118 end
1119 end
1120 else
1121 begin
1122 Result := FLine[FLineIndex];
1123 Inc(FLineIndex);
1124 end;
1125 end;
1126
1127 constructor TInteractiveSQLStatementReader.Create(aPrompt: string; aContinue: string);
1128 begin
1129 inherited Create;
1130 FPrompt := aPrompt;
1131 FLineIndex := 0;
1132 FNextStatement := true;
1133 FContinuePrompt := aContinue;
1134 end;
1135
1136 function TInteractiveSQLStatementReader.GetNextStatement(var stmt: string
1137 ): boolean;
1138 begin
1139 Result := inherited GetNextStatement(stmt);
1140 FNextStatement := Result;
1141 end;
1142
1143 { TBatchSQLStatementReader }
1144
1145 function TBatchSQLStatementReader.GetChar: AnsiChar;
1146 begin
1147 if not EOF and assigned(FInStream) and not (FInStream.Position = FInStream.Size) then
1148 begin
1149 Result := char(FInStream.ReadByte);
1150 if Result = LF then
1151 begin
1152 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 if Result <> CR then
1161 begin
1162 FCurLine += Result;
1163 Inc(FIndex);
1164 end;
1165 end
1166 else
1167 Result := #0;
1168 end;
1169
1170 function TBatchSQLStatementReader.GetErrorPrefix: AnsiString;
1171 begin
1172 Result := Format(sOnLineError,[FLineIndex,FIndex]);
1173 end;
1174
1175 procedure TBatchSQLStatementReader.Reset;
1176 begin
1177 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 FCurLine := '';
1185 end;
1186
1187 procedure TBatchSQLStatementReader.SetStreamSource(Lines: TStrings);
1188 begin
1189 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 end;
1197
1198 procedure TBatchSQLStatementReader.SetStreamSource(S: TStream);
1199 begin
1200 Reset;
1201 FInStream := S;
1202 if assigned(OnProgressEvent) then
1203 OnProgressEvent(self,true,S.Size - S.Position);
1204 end;
1205
1206 procedure TBatchSQLStatementReader.SetStreamSource(FileName: string);
1207 begin
1208 Reset;
1209 FInStream := TFileStream.Create(FileName,fmShareCompat);
1210 FOwnsInStream := true;
1211 if assigned(OnProgressEvent) then
1212 OnProgressEvent(self,true,FInStream.Size);
1213 end;
1214
1215 procedure TBatchSQLStatementReader.SetStringStreamSource(S: string);
1216 begin
1217 Reset;
1218 FInStream := TStringStream.Create(S);
1219 FOwnsInStream := true;
1220 if assigned(OnProgressEvent) then
1221 OnProgressEvent(self,true,FInStream.Size);
1222 end;
1223
1224 end.
1225

Properties

Name Value
svn:eol-style native