ViewVC Help
View File | Revision Log | Show Annotations | Download File | View Changeset | Root Listing
root/public/ibx/trunk/runtime/nongui/ibxscript.pas
Revision: 438
Committed: Mon Jul 22 14:15:33 2024 UTC (4 months ago) by tony
Content type: text/x-pascal
File size: 37240 byte(s)
Log Message:
Fixes Merged!

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 Set Time Zone}
972 RegexObj.Expression := '^ *SET +TIME +ZONE +(LOCAL|''[A-Za-z0-9/]+'') *(\' + Terminator + '|)';
973 if RegexObj.Exec(stmt) then
974 begin
975 {pass through}
976 Database.Attachment.ExecImmediate([isc_tpb_write,isc_tpb_wait,isc_tpb_concurrency],stmt);
977 Exit;
978 end;
979 {Process Remaining Set statements}
980 RegexObj.Expression := '^ *SET +([A-Z]+)( +[A-Z0-9]+|) *(\' + Terminator + '|)';
981 if RegexObj.Exec(stmt) then
982 begin
983 command := AnsiUpperCase(RegexObj.Match[1]);
984 param := trim(RegexObj.Match[2]);
985 if command = 'GENERATOR' then
986 begin
987 Result := false;
988 Exit;
989 end;
990 if command = 'AUTODDL' then
991 AutoDDL := ((RegexObj.MatchLen[2] = 0) and not AutoDDL) or
992 (RegexObj.MatchLen[2] > 0) and Toggle(param)
993 else
994 if command = 'BAIL' then
995 StopOnFirstError := ((RegexObj.MatchLen[2] = 0) and not StopOnFirstError) or
996 (RegexObj.MatchLen[2] > 0) and Toggle(param)
997 else
998 if command = 'ECHO' then
999 Echo := ((RegexObj.MatchLen[2] = 0) and not Echo) or
1000 (RegexObj.MatchLen[2] > 0) and Toggle(param)
1001 else
1002 if command = 'COUNT' then
1003 ShowAffectedRows := ((RegexObj.MatchLen[2] = 0) and not ShowAffectedRows) or
1004 (RegexObj.MatchLen[2] > 0) and Toggle(param)
1005 else
1006 if command = 'STATS' then
1007 ShowPerformanceStats := ((RegexObj.MatchLen[2] = 0) and not FShowPerformanceStats) or
1008 (RegexObj.MatchLen[2] > 0) and Toggle(param)
1009 else
1010 if command = 'NAMES' then
1011 begin
1012 if Database.Attachment.CharSetName2CharSetID(param,charsetid) then
1013 begin
1014 DBConnected := Database.Connected;
1015 LoginPrompt := Database.LoginPrompt;
1016 Database.LoginPrompt := false;
1017 Database.Connected := false;
1018 Database.Params.Values['lc_ctype'] := param;
1019 Database.Connected := DBConnected;
1020 Database.LoginPrompt := LoginPrompt;
1021 end
1022 else
1023 raise Exception.CreateFmt(sInvalidCharacterSet, [param,stmt]);
1024 end
1025 else
1026 begin
1027 if assigned(DataOutputFormatter) then
1028 DataOutputFormatter.SetCommand(command,param,stmt,Result);
1029 if not Result then
1030 begin
1031 if assigned(OnSetStatement) then
1032 OnSetStatement(self,command,param,stmt,Result)
1033 else
1034 raise Exception.CreateFmt(sInvalidSetStatement, [command,stmt]);
1035 end;
1036 Exit;
1037 end;
1038 Result := true;
1039 Exit;
1040 end;
1041
1042 finally
1043 RegexObj.Free;
1044 end;
1045 end;
1046
1047 procedure TCustomIBXScript.SetTransaction(AValue: TIBTransaction);
1048 begin
1049 if FTransaction = AValue then Exit;
1050 FTransaction := AValue;
1051 end;
1052
1053 constructor TCustomIBXScript.Create(aOwner: TComponent);
1054 begin
1055 inherited Create(aOwner);
1056 FStopOnFirstError := true;
1057 FEcho := true;
1058 FAutoDDL := true;
1059 FISQL := TIBSQL.Create(self);
1060 FISQL.ParamCheck := true;
1061 FInternalTransaction := TIBTransaction.Create(self);
1062 FInternalTransaction.Params.Clear;
1063 FInternalTransaction.Params.Add('concurrency');
1064 FInternalTransaction.Params.Add('wait');
1065 end;
1066
1067 destructor TCustomIBXScript.Destroy;
1068 begin
1069 if FSQLReader <> nil then FSQLReader.Free;
1070 if FISQL <> nil then FISQL.Free;
1071 if FInternalTransaction <> nil then FInternalTransaction.Free;
1072 inherited Destroy;
1073 end;
1074
1075 procedure TCustomIBXScript.DefaultSelectSQLHandler(aSQLText: string);
1076 begin
1077 if assigned(DataOutputFormatter) then
1078 DataOutputFormatter.DataOut(aSQLText,@Add2Log)
1079 else
1080 FSQLReader.ShowError(sNoSelectSQL);
1081 end;
1082
1083 { TInteractiveSQLStatementReader }
1084
1085 function TInteractiveSQLStatementReader.GetErrorPrefix: AnsiString;
1086 begin
1087 Result := '';
1088 end;
1089
1090 function TInteractiveSQLStatementReader.GetNextLine(var Line: string): boolean;
1091 begin
1092 if FNextStatement then
1093 write(FPrompt)
1094 else
1095 write(FContinuePrompt);
1096 Result := not system.EOF;
1097 if Result then
1098 begin
1099 readln(Line);
1100 EchoNextLine(Line);
1101 end;
1102 end;
1103
1104 function TInteractiveSQLStatementReader.GetChar: AnsiChar;
1105 begin
1106 if Terminated then
1107 Result := #0
1108 else
1109 if FLineIndex > Length(FLine) then
1110 begin
1111 Result := LF;
1112 FLineIndex := 0;
1113 end
1114 else
1115 if FLineIndex = 0 then
1116 begin
1117 if not GetNextLine(FLine) then
1118 Result := #0
1119 else
1120 if Length(FLine) = 0 then
1121 Result := LF
1122 else
1123 begin
1124 Result := FLine[1];
1125 FLineIndex := 2;
1126 end
1127 end
1128 else
1129 begin
1130 Result := FLine[FLineIndex];
1131 Inc(FLineIndex);
1132 end;
1133 end;
1134
1135 constructor TInteractiveSQLStatementReader.Create(aPrompt: string; aContinue: string);
1136 begin
1137 inherited Create;
1138 FPrompt := aPrompt;
1139 FLineIndex := 0;
1140 FNextStatement := true;
1141 FContinuePrompt := aContinue;
1142 end;
1143
1144 function TInteractiveSQLStatementReader.GetNextStatement(var stmt: string
1145 ): boolean;
1146 begin
1147 Result := inherited GetNextStatement(stmt);
1148 FNextStatement := Result;
1149 end;
1150
1151 { TBatchSQLStatementReader }
1152
1153 function TBatchSQLStatementReader.GetChar: AnsiChar;
1154 begin
1155 if not EOF and assigned(FInStream) and not (FInStream.Position = FInStream.Size) then
1156 begin
1157 Result := char(FInStream.ReadByte);
1158 if Result = LF then
1159 begin
1160 EchoNextLine(FCurLine);
1161 FCurLine := '';
1162 if assigned(OnProgressEvent) then
1163 OnProgressEvent(self,false,FIndex+1);
1164 Inc(FLineIndex);
1165 FIndex := 1;
1166 end
1167 else
1168 if Result <> CR then
1169 begin
1170 FCurLine += Result;
1171 Inc(FIndex);
1172 end;
1173 end
1174 else
1175 Result := #0;
1176 end;
1177
1178 function TBatchSQLStatementReader.GetErrorPrefix: AnsiString;
1179 begin
1180 Result := Format(sOnLineError,[FLineIndex,FIndex]);
1181 end;
1182
1183 procedure TBatchSQLStatementReader.Reset;
1184 begin
1185 inherited Reset;
1186 if FOwnsInStream and assigned(FInStream) then
1187 FInStream.Free;
1188 FInStream := nil;
1189 FOwnsInStream := false;
1190 FLineIndex := 1;
1191 FIndex := 1;
1192 FCurLine := '';
1193 end;
1194
1195 procedure TBatchSQLStatementReader.SetStreamSource(Lines: TStrings);
1196 begin
1197 Reset;
1198 FInStream := TMemoryStream.Create;
1199 FOwnsInStream := true;
1200 Lines.SaveToStream(FInStream);
1201 FInStream.Position := 0;
1202 if assigned(OnProgressEvent) then
1203 OnProgressEvent(self,true,FInStream.Size);
1204 end;
1205
1206 procedure TBatchSQLStatementReader.SetStreamSource(S: TStream);
1207 begin
1208 Reset;
1209 FInStream := S;
1210 if assigned(OnProgressEvent) then
1211 OnProgressEvent(self,true,S.Size - S.Position);
1212 end;
1213
1214 procedure TBatchSQLStatementReader.SetStreamSource(FileName: string);
1215 begin
1216 Reset;
1217 FInStream := TFileStream.Create(FileName,fmShareCompat);
1218 FOwnsInStream := true;
1219 if assigned(OnProgressEvent) then
1220 OnProgressEvent(self,true,FInStream.Size);
1221 end;
1222
1223 procedure TBatchSQLStatementReader.SetStringStreamSource(S: string);
1224 begin
1225 Reset;
1226 FInStream := TStringStream.Create(S);
1227 FOwnsInStream := true;
1228 if assigned(OnProgressEvent) then
1229 OnProgressEvent(self,true,FInStream.Size);
1230 end;
1231
1232 end.
1233

Properties

Name Value
svn:eol-style native