zeoslib  UNKNOWN
 All Files
ZSqlProcessor.pas
Go to the documentation of this file.
1 {*********************************************************}
2 { }
3 { Zeos Database Objects }
4 { Unidatabase SQLProcessor component }
5 { }
6 { Originally written by Sergey Seroukhov }
7 { }
8 {*********************************************************}
9 
10 {@********************************************************}
11 { Copyright (c) 1999-2012 Zeos Development Group }
12 { }
13 { License Agreement: }
14 { }
15 { This library is distributed in the hope that it will be }
16 { useful, but WITHOUT ANY WARRANTY; without even the }
17 { implied warranty of MERCHANTABILITY or FITNESS FOR }
18 { A PARTICULAR PURPOSE. See the GNU Lesser General }
19 { Public License for more details. }
20 { }
21 { The source code of the ZEOS Libraries and packages are }
22 { distributed under the Library GNU General Public }
23 { License (see the file COPYING / COPYING.ZEOS) }
24 { with the following modification: }
25 { As a special exception, the copyright holders of this }
26 { library give you permission to link this library with }
27 { independent modules to produce an executable, }
28 { regardless of the license terms of these independent }
29 { modules, and to copy and distribute the resulting }
30 { executable under terms of your choice, provided that }
31 { you also meet, for each linked independent module, }
32 { the terms and conditions of the license of that module. }
33 { An independent module is a module which is not derived }
34 { from or based on this library. If you modify this }
35 { library, you may extend this exception to your version }
36 { of the library, but you are not obligated to do so. }
37 { If you do not wish to do so, delete this exception }
38 { statement from your version. }
39 { }
40 { }
41 { The project web site is located on: }
42 { http://zeos.firmos.at (FORUM) }
43 { http://sourceforge.net/p/zeoslib/tickets/ (BUGTRACKER)}
44 { svn://svn.code.sf.net/p/zeoslib/code-0/trunk (SVN) }
45 { }
46 { http://www.sourceforge.net/projects/zeoslib. }
47 { }
48 { }
49 { Zeos Development Group. }
50 {********************************************************@}
51 
52 unit ZSqlProcessor;
53 
54 interface
55 
56 {$I ZComponent.inc}
57 
58 uses Types, Classes, SysUtils, {$IFDEF MSEgui}mclasses, mdb{$ELSE}DB{$ENDIF},
59  ZDbcIntfs, ZAbstractConnection, ZScriptParser, ZSqlStrings, ZCompatibility;
60 
61 type
62 
63  {** Forward definition of TZSQLProcessor. }
64  TZSQLProcessor = class;
65 
66  {** Defines an error handle action. }
67  TZErrorHandleAction = (eaFail, eaAbort, eaSkip, eaRetry);
68 
69  {** Defines an Processor notification event. }
70  TZProcessorNotifyEvent = procedure(Processor: TZSQLProcessor;
71  StatementIndex: Integer) of object;
72 
73  {** Defines an Processor error handling event. }
74  TZProcessorErrorEvent = procedure(Processor: TZSQLProcessor;
75  StatementIndex: Integer; E: Exception;
76  var ErrorHandleAction: TZErrorHandleAction) of object;
77 
78  {**
79  Implements a unidatabase component which parses and executes SQL Scripts.
80  }
81 
82  { TZSQLProcessor }
83 
84  TZSQLProcessor = class (TComponent)
85  private
86  FParams: TParams;
87  FScript: TZSQLStrings;
88  FScriptParser: TZSQLScriptParser;
89  FConnection: TZAbstractConnection;
90  FBeforeExecute: TZProcessorNotifyEvent;
91  FAfterExecute: TZProcessorNotifyEvent;
92  FOnError: TZProcessorErrorEvent;
93 
94  procedure SetParams(Value: TParams);
95  function GetScript: TStrings;
96  procedure SetScript(Value: TStrings);
97  function GetStatementCount: Integer;
98  function GetStatement(Index: Integer): string;
99  procedure SetConnection(Value: TZAbstractConnection);
100  function GetDelimiterType: TZDelimiterType;
101  procedure SetDelimiterType(Value: TZDelimiterType);
102  function GetDelimiter: string;
103  procedure SetDelimiter(const Value: string);
104  function GetCleanupStatements: Boolean;
105  procedure SetCleanupStatements(const Value: Boolean);
106 
107  function GetParamCheck: Boolean;
108  procedure SetParamCheck(Value: Boolean);
109  function GetParamChar: Char;
110  procedure SetParamChar(Value: Char);
111  procedure UpdateSQLStrings(Sender: TObject);
112  protected
113  procedure CheckConnected;
114  function DoOnError(StatementIndex: Integer; E: Exception):
115  TZErrorHandleAction;
116  procedure DoBeforeExecute(StatementIndex: Integer);
117  procedure DoAfterExecute(StatementIndex: Integer);
118 
119  function CreateStatement(const SQL: string; Properties: TStrings):
120  IZPreparedStatement; virtual;
121  procedure SetStatementParams(Statement: IZPreparedStatement;
122  const ParamNames: TStringDynArray; Params: TParams); virtual;
123  public
124  constructor Create(AOwner: TComponent); override;
125  destructor Destroy; override;
126 
127  procedure LoadFromStream(Stream: TStream);
128  procedure LoadFromFile(const FileName: string);
129 
130  procedure Execute;
131  procedure Parse;
132  procedure Clear;
133 
134  function ParamByName(const Value: string): TParam;
135 
136  property StatementCount: Integer read GetStatementCount;
137  property Statements[Index: Integer]: string read GetStatement;
138  published
139  property ParamCheck: Boolean read GetParamCheck write SetParamCheck
140  default True;
141  property ParamChar: Char read GetParamChar write SetParamChar
142  default ':';
143  property Params: TParams read FParams write SetParams;
144  property Script: TStrings read GetScript write SetScript;
145  property Connection: TZAbstractConnection read FConnection write SetConnection;
146  property DelimiterType: TZDelimiterType read GetDelimiterType
147  write SetDelimiterType default dtDefault;
148  property Delimiter: string read GetDelimiter write SetDelimiter;
149  property CleanupStatements: Boolean read GetCleanupStatements
150  write SetCleanupStatements default False;
151  property OnError: TZProcessorErrorEvent read FOnError write FOnError;
152  property AfterExecute: TZProcessorNotifyEvent read FAfterExecute write FAfterExecute;
153  property BeforeExecute: TZProcessorNotifyEvent read FBeforeExecute write FBeforeExecute;
154  end;
155 
156 implementation
157 
158 uses ZMessages, ZDbcUtils, ZAbstractRODataset, ZDatasetUtils;
159 
160 { TZSQLProcessor }
161 
162 {**
163  Creates this Processor component and assignes the main properties.
164  @param AOwner an owner component.
165 }
166 constructor TZSQLProcessor.Create(AOwner: TComponent);
167 begin
168  inherited Create(AOwner);
169 
170  FParams := TParams.Create(Self);
171  FScript := TZSQLStrings.Create;
172  FScript.Dataset := Self;
173  FScript.OnChange := UpdateSQLStrings;
174  FScriptParser := TZSQLScriptParser.Create;
175  FScriptParser.DelimiterType := dtDefault;
176  FScriptParser.Delimiter := ';';
177  FScriptParser.CleanupStatements := False;
178 end;
179 
180 {**
181  Destroys this component and cleanups the memory.
182 }
183 destructor TZSQLProcessor.Destroy;
184 begin
185  FreeAndNil(FParams);
186  FreeAndNil(FScript);
187  FreeAndNil(FScriptParser);
188  FConnection := nil;
189  inherited Destroy;
190 end;
191 
192 {**
193  Gets a parsed statement by it's index.
194  @return a SQL statement.
195 }
196 function TZSQLProcessor.GetStatement(Index: Integer): string;
197 begin
198  if (FScriptParser.UncompletedStatement <> '')
199  and (Index = FScriptParser.StatementCount) then
200  Result := FScriptParser.UncompletedStatement
201  else Result := FScriptParser.Statements[Index];
202 end;
203 
204 {**
205  Gets a statements count.
206  @return a number of parsed statements.
207 }
208 function TZSQLProcessor.GetStatementCount: Integer;
209 begin
210  Result := FScriptParser.StatementCount;
211  if FScriptParser.UncompletedStatement <> '' then
212  Inc(Result);
213 end;
214 
215 {**
216  Sets a new SQL connection component.
217  @param Value am SQL connection component.
218 }
219 procedure TZSQLProcessor.SetConnection(Value: TZAbstractConnection);
220 begin
221  if FConnection <> Value then
222  begin
223  FConnection := Value;
224  FScriptParser.ClearUncompleted;
225  end;
226 end;
227 
228 {**
229  Gets a script delimiter type;
230 }
231 function TZSQLProcessor.GetDelimiterType: TZDelimiterType;
232 begin
233  Result := FScriptParser.DelimiterType;
234 end;
235 
236 {**
237  Sets a new Processor delimiter type.
238  @param Value a new Processor delimiter type.
239 }
240 procedure TZSQLProcessor.SetDelimiterType(Value: TZDelimiterType);
241 begin
242  if FScriptParser.DelimiterType <> Value then
243  begin
244  FScriptParser.DelimiterType := Value;
245  FScriptParser.ClearUncompleted;
246  end;
247 end;
248 
249 {**
250  Gets a script delimiter;
251 }
252 function TZSQLProcessor.GetDelimiter: string;
253 begin
254  Result := FScriptParser.Delimiter;
255 end;
256 
257 {**
258  Sets a new Processor delimiter.
259  @param Value a new Processor delimiter.
260 }
261 procedure TZSQLProcessor.SetDelimiter(const Value: string);
262 begin
263  if FScriptParser.Delimiter <> Value then
264  begin
265  FScriptParser.Delimiter := Value;
266  FScriptParser.ClearUncompleted;
267  end;
268 end;
269 
270 {**
271  Sets a new set of parameters.
272  @param Value a set of parameters.
273 }
274 procedure TZSQLProcessor.SetParams(Value: TParams);
275 begin
276  FParams.AssignValues(Value);
277 end;
278 
279 {**
280  Sets a new SQL script.
281  @param Value a new SQL script.
282 }
283 procedure TZSQLProcessor.SetScript(Value: TStrings);
284 begin
285  FScript.Assign(Value);
286  FScriptParser.ClearUncompleted;
287 end;
288 
289 {**
290  Checks is the database connection assignes and tries to connect.
291 }
292 procedure TZSQLProcessor.CheckConnected;
293 begin
294  if Connection = nil then
295  raise EZDatabaseError.Create(SConnectionIsNotAssigned);
296  Connection.Connect;
297 end;
298 
299 {**
300  Clears Processor contents and all parsed statements.
301 }
302 procedure TZSQLProcessor.Clear;
303 begin
304  FScript.Clear;
305  FScriptParser.ClearUncompleted;
306 end;
307 
308 {**
309  Performs OnError Event and returns an error handle action.
310  @param StatementIndex an index of the statement which failt.
311  @param E an exception object.
312  @return an error handle action.
313 }
314 function TZSQLProcessor.DoOnError(StatementIndex: Integer;
315  E: Exception): TZErrorHandleAction;
316 begin
317  Result := eaFail;
318  if Assigned(FOnError) then
319  FOnError(Self, StatementIndex, E, Result);
320 end;
321 
322 {**
323  Performs an action before execute a statement.
324  @param StatementIndex an index of the executing statement.
325 }
326 procedure TZSQLProcessor.DoBeforeExecute(StatementIndex: Integer);
327 begin
328  if Assigned(FBeforeExecute) then
329  FBeforeExecute(Self, StatementIndex);
330 end;
331 
332 {**
333  Performs an action action execute a statement.
334  @param StatementIndex an index of the executing statement.
335 }
336 procedure TZSQLProcessor.DoAfterExecute(StatementIndex: Integer);
337 begin
338  if Assigned(FAfterExecute) then
339  FAfterExecute(Self, StatementIndex);
340 end;
341 
342 {**
343  Loads a SQL Processor from the local file.
344  @param FileName a name of the file.
345 }
346 procedure TZSQLProcessor.LoadFromFile(const FileName: string);
347 begin
348  FScript.LoadFromFile(FileName);
349 end;
350 
351 {**
352  Loads a SQL Processor from the stream.
353  @param Stream a stream object.
354 }
355 procedure TZSQLProcessor.LoadFromStream(Stream: TStream);
356 begin
357  FScript.LoadFromStream(Stream);
358 end;
359 
360 {**
361  Executes a parsed SQL Processor.
362 }
363 procedure TZSQLProcessor.Execute;
364 var
365  I: Integer;
366  Statement: IZPreparedStatement;
367  Action: TZErrorHandleAction;
368  SQL: TZSQLStrings;
369 begin
370  if Connection = nil then
371  raise EZDatabaseError.Create(SConnectionIsNotAssigned);
372 
373  FConnection.ShowSQLHourGlass;
374  try
375  SQL := TZSQLStrings.Create;
376  SQL.Dataset := Self;
377  SQL.ParamCheck := FScript.ParamCheck;
378  SQL.MultiStatements := False;
379  Parse;
380 
381  for I := 0 to Pred(StatementCount) do
382  begin
383  Action := eaSkip;
384  DoBeforeExecute(I);
385  repeat
386  try
387  SQL.Text := GetStatement(I);
388 {http://zeos.firmos.at/viewtopic.php?t=2885&start=0&postdays=0&postorder=asc&highlight=}
389  if SQL.StatementCount > 0 then
390  begin
391  Statement := CreateStatement(SQL.Statements[0].SQL, nil);
392  SetStatementParams(Statement, SQL.Statements[0].ParamNamesArray,
393  FParams);
394  Statement.ExecuteUpdatePrepared;
395  end;
396  Statement := nil;
397  except
398  on E: Exception do
399  begin
400  if Assigned(Statement) then
401  Statement := nil;
402  Action := DoOnError(I, E);
403  if Action = eaFail then
404  RaiseSQLException(E)
405  else if Action = eaAbort then
406  Exit;
407  end;
408  end;
409  until Action <> eaRetry;
410  DoAfterExecute(I);
411 
412  end;
413  finally
414  FreeAndNil(SQL);
415  Connection.HideSQLHourGlass;
416  end;
417 end;
418 
419 {**
420  Gets a SQL parameter by its name.
421  @param Value a parameter name.
422  @return a found parameter object.
423 }
424 function TZSQLProcessor.ParamByName(const Value: string): TParam;
425 begin
426  Result := FParams.ParamByName(Value);
427 end;
428 
429 {**
430  Parses the loaded SQL Processor.
431 }
432 procedure TZSQLProcessor.Parse;
433 begin
434  CheckConnected;
435  FScriptParser.Tokenizer := Connection.DbcDriver.GetTokenizer;
436 // mdaems 20060429 : Clear would reset the delimiter of the scriptparser
437 // FScriptParser.Clear;
438  FScriptParser.ClearUncompleted;
439  FScriptParser.ParseText(FScript.Text);
440 end;
441 
442 {**
443  Creates a DBC statement for the query.
444  @param SQL an SQL query.
445  @param Properties a statement specific properties.
446  @returns a created DBC statement.
447 }
448 function TZSQLProcessor.CreateStatement(const SQL: string;
449  Properties: TStrings): IZPreparedStatement;
450 var
451  Temp: TStrings;
452 begin
453  Temp := TStringList.Create;
454  try
455  if Assigned(Properties) then
456  Temp.AddStrings(Properties);
457 
458  Result := FConnection.DbcConnection.PrepareStatementWithParams(SQL, Temp);
459  finally
460  Temp.Free;
461  end;
462 end;
463 
464 {**
465  Fill prepared statement with parameters.
466  @param Statement a prepared SQL statement.
467  @param ParamNames an array of parameter names.
468  @param Params a collection of SQL parameters.
469 }
470 procedure TZSQLProcessor.SetStatementParams(Statement: IZPreparedStatement;
471  const ParamNames: TStringDynArray; Params: TParams);
472 var
473  I: Integer;
474  TempParam, Param: TParam;
475 begin
476  TempParam := TParam.Create(nil);
477 
478  try
479  for I := Low(ParamNames) to High(ParamNames) do
480  begin
481  Param := Params.FindParam(ParamNames[I]);
482  if not Assigned(Param) or (Param.ParamType in [ptOutput, ptResult]) then
483  Continue;
484  SetStatementParam(I+1, Statement, Param);
485  end;
486  finally
487  TempParam.Free;
488  end;
489 end;
490 
491 {**
492  Gets the SQL script.
493  @return the SQL script strings.
494 }
495 function TZSQLProcessor.GetScript: TStrings;
496 begin
497  Result := FScript;
498 end;
499 
500 {**
501  Updates parameters from SQL statement.
502  @param Sender an event sender object.
503 }
504 procedure TZSQLProcessor.UpdateSQLStrings(Sender: TObject);
505 var
506  I: Integer;
507  OldParams: TParams;
508 begin
509  OldParams := TParams.Create;
510  OldParams.Assign(FParams);
511  FParams.Clear;
512 
513  try
514  for I := 0 to FScript.ParamCount - 1 do
515  FParams.CreateParam(ftUnknown, FScript.ParamNames[I], ptUnknown);
516  FParams.AssignValues(OldParams);
517  finally
518  OldParams.Free;
519  end;
520 end;
521 
522 {**
523  Gets a parameters check value.
524  @return a parameters check value.
525 }
526 function TZSQLProcessor.GetParamCheck: Boolean;
527 begin
528  Result := FScript.ParamCheck;
529 end;
530 
531 {**
532  Sets a new parameters check value.
533  @param Value a parameters check value.
534 }
535 procedure TZSQLProcessor.SetParamCheck(Value: Boolean);
536 begin
537  FScript.ParamCheck := Value;
538  UpdateSQLStrings(Self);
539 end;
540 
541 {**
542  Gets a parameters marker.
543  @return a parameter marker.
544 }
545 function TZSQLProcessor.GetParamChar: Char;
546 begin
547  Result := FScript.ParamChar;
548 end;
549 
550 {**
551  Sets a new parameter marker.
552  @param Value a parameter marker.
553 }
554 procedure TZSQLProcessor.SetParamChar(Value: Char);
555 begin
556  FScript.ParamChar := Value;
557  UpdateSQLStrings(Self);
558 end;
559 
560 function TZSQLProcessor.GetCleanupStatements: Boolean;
561 begin
562  Result := FScriptParser.CleanupStatements;
563 end;
564 
565 procedure TZSQLProcessor.SetCleanupStatements(const Value: Boolean);
566 begin
567  if FScriptParser.CleanupStatements <> Value then
568  begin
569  FScriptParser.CleanupStatements := Value;
570  FScriptParser.ClearUncompleted;
571  end;
572 end;
573 
574 end.
575