zeoslib  UNKNOWN
 All Files
ZMySqlToken.pas
Go to the documentation of this file.
1 {*********************************************************}
2 { }
3 { Zeos Database Objects }
4 { String tokenizing classes for MySQL }
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 ZMySqlToken;
53 
54 interface
55 
56 {$I ZParseSql.inc}
57 
58 uses
59  Classes, {$IFDEF MSEgui}mclasses,{$ENDIF} SysUtils,
60  ZSysUtils, ZTokenizer, ZGenericSqlToken, ZCompatibility;
61 
62 type
63 
64  {** Implements a MySQL-specific number state object. }
65  TZMySQLNumberState = class (TZNumberState)
66  public
67  function NextToken(Stream: TStream; FirstChar: Char;
68  Tokenizer: TZTokenizer): TZToken; override;
69  end;
70 
71  {** Implements a MySQL-specific quote string state object. }
72  TZMySQLQuoteState = class (TZQuoteState)
73  public
74  function NextToken(Stream: TStream; FirstChar: Char;
75  Tokenizer: TZTokenizer): TZToken; override;
76 
77  function EncodeString(const Value: string; QuoteChar: Char): string; override;
78  function DecodeString(const Value: string; QuoteChar: Char): string; override;
79  end;
80 
81  {**
82  This state will either delegate to a comment-handling
83  state, or return a token with just a slash in it.
84  }
85  TZMySQLCommentState = class (TZCppCommentState)
86  public
87  function NextToken(Stream: TStream; FirstChar: Char;
88  Tokenizer: TZTokenizer): TZToken; override;
89  end;
90 
91  {** Implements a symbol state object. }
92  TZMySQLSymbolState = class (TZSymbolState)
93  public
94  constructor Create;
95  end;
96 
97  {** Implements a word state object. }
98  TZMySQLWordState = class (TZGenericSQLWordState)
99  public
100  constructor Create;
101  end;
102 
103  {** Implements a default tokenizer object. }
104  TZMySQLTokenizer = class (TZTokenizer)
105  public
106  constructor Create;
107  end;
108 
109 implementation
110 
111 uses StrUtils;
112 
113 { TZMySQLNumberState }
114 
115 {**
116  Return a number token from a reader.
117  @return a number token from a reader
118 }
119 function TZMySQLNumberState.NextToken(Stream: TStream; FirstChar: Char;
120  Tokenizer: TZTokenizer): TZToken;
121 var
122  HexDecimal: Boolean;
123  FloatPoint: Boolean;
124  LastChar: Char;
125 
126  function ReadHexDigits: string;
127  begin
128  Result := '';
129  LastChar := #0;
130  while Stream.Read(LastChar, SizeOf(Char)) > 0 do
131  begin
132  if CharInSet(LastChar, ['0'..'9','a'..'f','A'..'F']) then
133  begin
134  Result := Result + LastChar;
135  HexDecimal := HexDecimal or CharInSet(LastChar, ['a'..'f','A'..'F']);
136  LastChar := #0;
137  end
138  else
139  begin
140  Stream.Seek(-SizeOf(Char), soFromCurrent);
141  Break;
142  end;
143  end;
144  end;
145 
146  function ReadDecDigits: string;
147  begin
148  Result := '';
149  LastChar := #0;
150  while Stream.Read(LastChar, SizeOf(Char)) > 0 do
151  begin
152  if CharInSet(LastChar, ['0'..'9']) then
153  begin
154  Result := Result + LastChar;
155  LastChar := #0;
156  end
157  else
158  begin
159  Stream.Seek(-SizeOf(Char), soFromCurrent);
160  Break;
161  end;
162  end;
163  end;
164 
165 begin
166  HexDecimal := False;
167  FloatPoint := FirstChar = '.';
168  Result.Value := FirstChar;
169  Result.TokenType := ttUnknown;
170  LastChar := #0;
171 
172  { Reads the first part of the number before decimal point }
173  if not FloatPoint then
174  begin
175  Result.Value := Result.Value + ReadDecDigits;
176  FloatPoint := (LastChar = '.') and not HexDecimal;
177  if FloatPoint then
178  begin
179  Stream.Read(LastChar, SizeOf(Char));
180  Result.Value := Result.Value + LastChar;
181  end;
182  end;
183 
184  { Reads the second part of the number after decimal point }
185  if FloatPoint then
186  Result.Value := Result.Value + ReadDecDigits;
187 
188  { Reads a power part of the number }
189  if not HexDecimal and CharInSet(LastChar, ['e','E']) then
190  begin
191  Stream.Read(LastChar, SizeOf(Char));
192  Result.Value := Result.Value + LastChar;
193  FloatPoint := True;
194 
195  Stream.Read(LastChar, SizeOf(Char));
196  if CharInSet(LastChar, ['0'..'9','-','+']) then
197  Result.Value := Result.Value + LastChar + ReadDecDigits
198  else
199  begin
200  Result.Value := Copy(Result.Value, 1, Length(Result.Value) - 1);
201  Stream.Seek(-2*SizeOf(Char), soFromCurrent);
202  end;
203  end;
204 
205  { Reads the nexdecimal number }
206  if (Result.Value = '0') and CharInSet(LastChar, ['x','X']) then
207  begin
208  Stream.Read(LastChar, SizeOf(Char));
209  Result.Value := Result.Value + LastChar + ReadHexDigits;
210  HexDecimal := True;
211  end;
212 
213  { Prepare the result }
214  if Result.Value = '.' then
215  begin
216  if Tokenizer.SymbolState <> nil then
217  Result := Tokenizer.SymbolState.NextToken(Stream, FirstChar, Tokenizer);
218  end
219  else
220  begin
221  if HexDecimal then
222  Result.TokenType := ttHexDecimal
223  else if FloatPoint then
224  Result.TokenType := ttFloat
225  else Result.TokenType := ttInteger;
226  end;
227 end;
228 
229 { TZMySQLQuoteState }
230 
231 {**
232  Return a quoted string token from a reader. This method
233  will collect characters until it sees a match to the
234  character that the tokenizer used to switch to this state.
235 
236  @return a quoted string token from a reader
237 }
238 function TZMySQLQuoteState.NextToken(Stream: TStream; FirstChar: Char;
239  Tokenizer: TZTokenizer): TZToken;
240 const BackSlash = Char('\');
241 var
242  ReadChar: Char;
243  LastChar: Char;
244  //QuoteChar: Char;
245  //QuoteCount: Integer;
246 begin
247  Result.Value := FirstChar;
248  //QuoteCount := 1;
249  If FirstChar = '`' then
250  Result.TokenType := ttQuotedIdentifier
251  Else
252  Result.TokenType := ttQuoted;
253 
254  //QuoteChar := FirstChar;
255 
256  LastChar := #0;
257 
258  while Stream.Read(ReadChar, SizeOf(Char)) > 0 do
259  begin
260  //if ReadChar = QuoteChar then Inc(QuoteCount);
261  if (LastChar = FirstChar) and (ReadChar <> FirstChar) then
262  begin
263  //if QuoteCount mod 2 = 0 then // only valid for Pascal AnsiQuoted/QuotedStr
264  begin
265  Stream.Seek(-SizeOf(Char), soFromCurrent);
266  Break;
267  end;
268  end;
269  Result.Value := Result.Value + ReadChar;
270  if LastChar = BackSlash then
271 // begin
272 // if Readchar = FirstChar then Inc(QuoteCount); //Escaped single Quote (A QuoteChar instead of FirstChar would be better..)
273  LastChar := #0
274 // end
275  else if (LastChar = FirstChar) and (ReadChar = FirstChar) then
276  LastChar := #0
277  else LastChar := ReadChar;
278  end;
279 end;
280 
281 {**
282  Encodes a string value.
283  @param Value a string value to be encoded.
284  @param QuoteChar a string quote character.
285  @returns an encoded string.
286 }
287 function TZMySQLQuoteState.EncodeString(const Value: string; QuoteChar: Char): string;
288 begin
289  if CharInSet(QuoteChar, [#39, '"', '`']) then
290  Result := QuoteChar + EncodeCString(Value) + QuoteChar
291  else Result := Value;
292 end;
293 
294 {**
295  Decodes a string value.
296  @param Value a string value to be decoded.
297  @param QuoteChar a string quote character.
298  @returns an decoded string.
299 }
300 function TZMySQLQuoteState.DecodeString(const Value: string; QuoteChar: Char): string;
301 var
302  Len: Integer;
303 begin
304  Len := Length(Value);
305  if (Len >= 2) and CharInSet(QuoteChar, [#39, '"', '`'])
306  and (Value[1] = QuoteChar) and (Value[Len] = QuoteChar) then
307  begin
308  if Len > 2 then
309  Result := DecodeCString(Copy(Value, 2, Len - 2))
310  else Result := '';
311  end
312  else Result := Value;
313 end;
314 
315 { TZMySQLCommentState }
316 
317 {**
318  Gets a MySQL specific comments like # or /* */.
319  @return either just a slash token, or the results of
320  delegating to a comment-handling state
321 }
322 function TZMySQLCommentState.NextToken(Stream: TStream; FirstChar: Char;
323  Tokenizer: TZTokenizer): TZToken;
324 var
325  ReadChar: Char;
326  ReadNum, ReadNum2: Integer;
327 begin
328  Result.TokenType := ttUnknown;
329  Result.Value := FirstChar;
330 
331  if FirstChar = '-' then
332  begin
333  ReadNum := Stream.Read(ReadChar, SizeOf(Char));
334  if (ReadNum > 0) and (ReadChar = '-') then
335  begin
336  Result.TokenType := ttComment;
337  Result.Value := '--' + GetSingleLineComment(Stream);
338  end
339  else
340  begin
341  if ReadNum > 0 then
342  Stream.Seek(-SizeOf(Char), soFromCurrent);
343  end;
344  end
345  else if FirstChar = '#' then
346  begin
347  Result.TokenType := ttComment;
348  Result.Value := '#' + GetSingleLineComment(Stream);
349  end
350  else if FirstChar = '/' then
351  begin
352  ReadNum := Stream.Read(ReadChar, SizeOf(Char));
353  if (ReadNum > 0) and (ReadChar = '*') then
354  begin
355  ReadNum2 := Stream.Read(ReadChar, SizeOf(Char));
356  // Don't treat '/*!' comments as normal comments!!
357  if (ReadNum2 > 0) and (ReadChar <> '!') then
358  begin
359  Result.TokenType := ttComment;
360  Result.Value := '/*'+ReadChar + GetMultiLineComment(Stream);
361  end
362  else
363  begin
364  if ReadNum2 > 0 then
365  Result.TokenType := ttSymbol;
366  Result.Value := '/*!' + GetMultiLineComment(Stream);
367  end;
368  end
369  else
370  begin
371  if ReadNum > 0 then
372  Stream.Seek(-SizeOf(Char), soFromCurrent);
373  end;
374  end;
375 
376  if (Result.TokenType = ttUnknown) and (Tokenizer.SymbolState <> nil) then
377  Result := Tokenizer.SymbolState.NextToken(Stream, FirstChar, Tokenizer);
378 end;
379 
380 { TZMySQLSymbolState }
381 
382 {**
383  Creates this MySQL-specific symbol state object.
384 }
385 constructor TZMySQLSymbolState.Create;
386 begin
387  inherited Create;
388  Add('<=');
389  Add('>=');
390  Add('<>');
391  Add('<<');
392  Add('>>');
393  {BEGIN PATCH: added by fduenas}
394  Add(':=');
395  {END PATCH: added by fduenas}
396 end;
397 
398 { TZMySQLWordState }
399 
400 {**
401  Constructs this MySQL-specific word state object.
402 }
403 constructor TZMySQLWordState.Create;
404 begin
405  SetWordChars(#0, #191, False);
406  SetWordChars(#192, high(char), True);
407  SetWordChars('a', 'z', True);
408  SetWordChars('A', 'Z', True);
409  SetWordChars('0', '9', True);
410  SetWordChars('$', '$', True);
411  SetWordChars('_', '_', True);
412 end;
413 
414 { TZMySQLTokenizer }
415 
416 {**
417  Constructs a tokenizer with a default state table (as
418  described in the class comment).
419 }
420 constructor TZMySQLTokenizer.Create;
421 begin
422  WhitespaceState := TZWhitespaceState.Create;
423 
424  EscapeState := TZEscapeState.Create;
425  SymbolState := TZMySQLSymbolState.Create;
426  NumberState := TZMySQLNumberState.Create;
427  QuoteState := TZMySQLQuoteState.Create;
428  WordState := TZMySQLWordState.Create;
429  CommentState := TZMySQLCommentState.Create;
430 
431  SetCharacterState(#0, #32, WhitespaceState);
432  SetCharacterState(#33, #191, SymbolState);
433  SetCharacterState(#192, High(Char), WordState);
434 
435  SetCharacterState('a', 'z', WordState);
436  SetCharacterState('A', 'Z', WordState);
437  SetCharacterState('_', '_', WordState);
438  SetCharacterState('$', '$', WordState);
439 
440  SetCharacterState('0', '9', NumberState);
441  SetCharacterState('.', '.', NumberState);
442 
443  SetCharacterState('"', '"', QuoteState);
444  SetCharacterState(#39, #39, QuoteState);
445  SetCharacterState('`', '`', QuoteState);
446 
447  SetCharacterState('/', '/', CommentState);
448  SetCharacterState('#', '#', CommentState);
449  SetCharacterState('-', '-', CommentState);
450 end;
451 
452 end.
453