zeoslib  UNKNOWN
 All Files
ZSqlStrings.pas
Go to the documentation of this file.
1 {*********************************************************}
2 { }
3 { Zeos Database Objects }
4 { SQL Query Strings 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 ZSqlStrings;
53 
54 interface
55 
56 {$I ZComponent.inc}
57 
58 uses
59  Types, Classes, SysUtils, {$IFDEF MSEgui}mclasses,{$ENDIF}Contnrs,
60  ZDbcIntfs, ZTokenizer, ZGenericSqlToken, ZCompatibility;
61 
62 type
63  {** Represents a SQL statement description object. }
64  TZSQLStatement = class (TObject)
65  private
66  FSQL: string;
67  FParamIndices: TIntegerDynArray;
68  FParams: TStrings;
69 
70  function GetParamCount: Integer;
71  function GetParamName(Index: Integer): string;
72  function GetParamNamesArray: TStringDynArray;
73  public
74  constructor Create(const SQL: string; const ParamIndices: TIntegerDynArray;
75  Params: TStrings);
76  property SQL: string read FSQL;
77  property ParamCount: Integer read GetParamCount;
78  property ParamNames[Index: Integer]: string read GetParamName;
79  property ParamIndices: TIntegerDynArray read FParamIndices;
80  property ParamNamesArray: TStringDynArray read GetParamNamesArray;
81  end;
82 
83  {** Imlements a string list with SQL statements. }
84 
85  { TZSQLStrings }
86 
87  TZSQLStrings = class (TStringList)
88  private
89  FDataset: TObject;
90  FParamCheck: Boolean;
91  FStatements: TObjectList;
92  FParams: TStringList;
93  FMultiStatements: Boolean;
94  FParamChar: Char;
95 
96  function GetParamCount: Integer;
97  function GetParamName(Index: Integer): string;
98  function GetStatement(Index: Integer): TZSQLStatement;
99  function GetStatementCount: Integer;
100  function GetTokenizer: IZTokenizer;
101  procedure SetDataset(Value: TObject);
102  procedure SetParamCheck(Value: Boolean);
103  procedure SetParamChar(Value: Char);
104  procedure SetMultiStatements(Value: Boolean);
105  protected
106  procedure Changed; override;
107  function FindParam(const ParamName: string): Integer;
108  procedure RebuildAll;
109  public
110  constructor Create;
111  destructor Destroy; override;
112 
113  property Dataset: TObject read FDataset write SetDataset;
114  property ParamCheck: Boolean read FParamCheck write SetParamCheck;
115  property ParamCount: Integer read GetParamCount;
116  property ParamChar: Char read FParamChar write SetParamChar;
117  property ParamNames[Index: Integer]: string read GetParamName;
118  property StatementCount: Integer read GetStatementCount;
119  property Statements[Index: Integer]: TZSQLStatement read GetStatement;
120  property MultiStatements: Boolean read FMultiStatements
121  write SetMultiStatements;
122  end;
123 
124 implementation
125 
126 uses ZMessages, ZAbstractRODataset, ZDatasetUtils, ZSqlProcessor;
127 
128 { TZSQLStatement }
129 
130 {**
131  Creates a SQL statement object and assignes the main properties.
132  @param SQL a SQL statement.
133  @param ParamIndices a parameter indices.
134  @param Params a list with all parameter names.
135 }
136 constructor TZSQLStatement.Create(const SQL: string;
137  const ParamIndices: TIntegerDynArray; Params: TStrings);
138 begin
139  FSQL := SQL;
140  FParamIndices := ParamIndices;
141  FParams := Params;
142 end;
143 
144 {**
145  Gets a parameters count for this statement.
146  @return a parameters count.
147 }
148 function TZSQLStatement.GetParamCount: Integer;
149 begin
150  if Assigned(FParamIndices) then
151  Result := High(FParamIndices) - Low(FParamIndices) + 1
152  else Result := 0;
153 end;
154 
155 {**
156  Gets a parameter name by it's index inside the statement.
157  @return a parameter name.
158 }
159 function TZSQLStatement.GetParamName(Index: Integer): string;
160 begin
161  if Assigned(FParamIndices) then
162  Result := FParams[FParamIndices[Index + Low(FParamIndices)]]
163  else Result := '';
164 end;
165 
166 {**
167  Gets an array of parameter names.
168  @return an array of parameter names.
169 }
170 function TZSQLStatement.GetParamNamesArray: TStringDynArray;
171 var
172  I: Integer;
173 begin
174  SetLength(Result, High(FParamIndices) - Low(FParamIndices) + 1);
175  for I := Low(Result) to High(Result) do
176  Result[I] := FParams[FParamIndices[I + Low(FParamIndices)]];
177 end;
178 
179 { TZSQLStrings }
180 
181 {**
182  Creates a SQL strings object and assigns the main properties.
183 }
184 constructor TZSQLStrings.Create;
185 begin
186  inherited Create; { -> needed to run the TestSuite else Inheritance(Self).Methods fails}
187  FParams := TStringList.Create;
188  FParamCheck := True;
189  FStatements := TObjectList.Create;
190  FMultiStatements := True;
191  FParamChar :=':';
192 end;
193 
194 {**
195  Destroys this object and cleanups the memory.
196 }
197 destructor TZSQLStrings.Destroy;
198 begin
199  FreeAndNil(FParams);
200  FreeAndNil(FStatements);
201  FDataSet := nil;
202  inherited Destroy;
203 end;
204 
205 {**
206  Gets a parameter count.
207  @return a count of SQL parameters.
208 }
209 function TZSQLStrings.GetParamCount: Integer;
210 begin
211  Result := FParams.Count;
212 end;
213 
214 {**
215  Gets parameter name by it's index.
216  @param Index a parameter index.
217  @return a parameter name.
218 }
219 function TZSQLStrings.GetParamName(Index: Integer): string;
220 begin
221  Result := FParams[Index];
222 end;
223 
224 {**
225  Gets a SQL statements count.
226  @return a SQL statements count.
227 }
228 function TZSQLStrings.GetStatementCount: Integer;
229 begin
230  Result := FStatements.Count;
231 end;
232 
233 function TZSQLStrings.GetTokenizer: IZTokenizer;
234 var
235  Driver: IZDriver;
236 begin
237  { Defines a SQL specific tokenizer object. }
238  Result := nil;
239  if FDataset is TZAbstractRODataset then
240  begin
241  if Assigned(TZAbstractRODataset(FDataset).Connection) then
242  begin
243  Driver := TZAbstractRODataset(FDataset).Connection.DbcDriver;
244  if Assigned(Driver) then
245  Result := Driver.GetTokenizer;
246  end;
247  end
248  else if FDataset is TZSQLProcessor then
249  if Assigned(TZSQLProcessor(FDataset).Connection) then
250  begin
251  Driver := TZSQLProcessor(FDataset).Connection.DbcDriver;
252  if Assigned(Driver) then
253  Result := Driver.GetTokenizer;
254  end;
255  if Result = nil then
256  Result := TZGenericSQLTokenizer.Create; { thread save! Allways return a new Tokenizer! }
257 end;
258 
259 {**
260  Gets a SQL statement by it's index.
261  @param Index a SQL statement index.
262  @return a SQL statement object.
263 }
264 function TZSQLStrings.GetStatement(Index: Integer): TZSQLStatement;
265 begin
266  Result := TZSQLStatement(FStatements[Index]);
267 end;
268 
269 {**
270  Sets a new ParamCheck value.
271  @param Value a new ParamCheck value.
272 }
273 procedure TZSQLStrings.SetParamCheck(Value: Boolean);
274 begin
275  if FParamCheck <> Value then
276  begin
277  FParamCheck := Value;
278  RebuildAll;
279  end;
280 end;
281 
282 {**
283  Sets a new ParamChar value.
284  @param Value a new ParamCheck value.
285 }
286 procedure TZSQLStrings.SetParamChar(Value: Char);
287 begin
288  if FParamChar <> Value then
289  begin
290  If not(GetTokenizer.GetCharacterState(Value) is TZSymbolstate) Then
291  raise EZDatabaseError.Create('Ongeldige ParamChar waarde : '+Value);
292  FParamChar := Value;
293  RebuildAll;
294  end;
295 end;
296 
297 {**
298  Sets a new MultiStatements value.
299  @param Value a new MultiStatements value.
300 }
301 procedure TZSQLStrings.SetMultiStatements(Value: Boolean);
302 begin
303  if FMultiStatements <> Value then
304  begin
305  FMultiStatements := Value;
306  RebuildAll;
307  end;
308 end;
309 
310 {**
311  Sets a new correspondent dataset object.
312  @param Value a new dataset object.
313 }
314 procedure TZSQLStrings.SetDataset(Value: TObject);
315 begin
316  if FDataset <> Value then
317  begin
318  FDataset := Value;
319  RebuildAll;
320  end;
321 end;
322 
323 {**
324  Finds a parameter by it's name.
325  @param ParamName a parameter name.
326  @return an index of found parameters or -1 if nothing was found.
327 }
328 function TZSQLStrings.FindParam(const ParamName: string): Integer;
329 begin
330  FParams.CaseSensitive := False;
331  Result := FParams.IndexOf(ParamName);
332 end;
333 
334 {**
335  Rebuilds all SQL statements.
336 }
337 procedure TZSQLStrings.RebuildAll;
338 var
339  Tokens: TStrings;
340  TokenValue: string;
341  TokenType: TZTokenType;
342  TokenIndex: Integer;
343  ParamIndex: Integer;
344  ParamIndices: TIntegerDynArray;
345  ParamIndexCount: Integer;
346  ParamName, SQL: string;
347  Tokenizer: IZTokenizer;
348 
349  procedure NextToken;
350  begin
351  TokenType := TZTokenType({$IFDEF FPC}Pointer({$ENDIF}
352  Tokens.Objects[TokenIndex]{$IFDEF FPC}){$ENDIF});
353  TokenValue := Tokens[TokenIndex];
354  Inc(TokenIndex);
355  end;
356 
357 begin
358  if not (Assigned(FParams) and Assigned(FStatements)) then exit; //Alexs
359 
360  FParams.Clear;
361  FStatements.Clear;
362  SQL := '';
363  ParamIndexCount := 0;
364  SetLength(ParamIndices, ParamIndexCount);
365 
366  { Optimization for empty query. }
367  If Length(Trim(Text)) = 0 then
368  Exit;
369 
370  { Optimization for single query without parameters. }
371  if (not FParamCheck or (Pos(FParamChar, Text) = 0))
372  and (not FMultiStatements or (Pos(';', Text) = 0)) then
373  begin
374  FStatements.Add(TZSQLStatement.Create(Text, ParamIndices, FParams));
375  Exit;
376  end;
377 
378  Tokenizer:=GetTokenizer;
379  Tokens := Tokenizer.TokenizeBufferToList(Text,
380  [toSkipComments, toUnifyWhitespaces]);
381  try
382  TokenIndex := 0;
383  repeat
384  NextToken;
385  { Processes parameters. }
386  if ParamCheck and (TokenValue = FParamChar) then
387  begin
388  NextToken;
389  if (TokenType <> ttEOF) and (TokenValue <> FParamChar) then
390  begin
391  { Check for correct parameter type. }
392  if not (TokenType in [ttWord, ttQuoted, ttQuotedIdentifier, ttKeyWord]) then
393  raise EZDatabaseError.Create(SIncorrectToken);
394 
395  SQL := SQL + '?';
396 
397  ParamName := TokenValue;
398  if (ParamName <> '') and CharInSet(ParamName[1], [#39, '`', '"', '[']) then
399  begin
400  ParamName := Tokenizer.GetQuoteState.
401  DecodeString(ParamName, ParamName[1]);
402  end;
403 
404  ParamIndex := FindParam(ParamName);
405  if ParamIndex < 0 then
406  ParamIndex := FParams.Add(ParamName);
407 
408  Inc(ParamIndexCount);
409  SetLength(ParamIndices, ParamIndexCount);
410  ParamIndices[ParamIndexCount - 1] := ParamIndex;
411 
412  Continue;
413  end;
414  end;
415 
416  { Adds a DML statement. }
417  if (TokenType = ttEOF) or (FMultiStatements and (TokenValue = ';')) then
418  begin
419  SQL := Trim(SQL);
420  if SQL <> '' then
421  FStatements.Add(TZSQLStatement.Create(SQL, ParamIndices, FParams));
422 
423  SQL := '';
424  ParamIndexCount := 0;
425  SetLength(ParamIndices, ParamIndexCount);
426  end
427  { Adds a default token. }
428  else
429  SQL := SQL + TokenValue;
430  until TokenType = ttEOF;
431  finally
432  Tokens.Free;
433  end;
434 end;
435 
436 {**
437  Performs action when the content of this string list is changed.
438 }
439 procedure TZSQLStrings.Changed;
440 begin
441  if UpdateCount = 0 then
442  RebuildAll;
443  inherited Changed;
444 end;
445 
446 end.