zeoslib  UNKNOWN
 All Files
ZDbcGenericResolver.pas
Go to the documentation of this file.
1 {*********************************************************}
2 { }
3 { Zeos Database Objects }
4 { Generic Cached Resolver }
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 ZDbcGenericResolver;
53 
54 interface
55 
56 {$I ZDbc.inc}
57 
58 uses
59  Types, Classes, {$IFDEF MSEgui}mclasses,{$ENDIF} SysUtils, Contnrs,
60  ZVariant, ZDbcIntfs, ZDbcCache, ZDbcCachedResultSet, ZCompatibility,
61  ZSelectSchema;
62 
63 type
64 
65  {** Implements a resolver parameter object. }
66  TZResolverParameter = class (TObject)
67  private
68  FColumnIndex: Integer;
69  FColumnName: string;
70  FColumnType: TZSQLType;
71  FNewValue: Boolean;
72  FDefaultValue: string;
73  public
74  constructor Create(ColumnIndex: Integer; ColumnName: string;
75  ColumnType: TZSQLType; NewValue: Boolean; DefaultValue: string);
76 
77  property ColumnIndex: Integer read FColumnIndex write FColumnIndex;
78  property ColumnName: string read FColumnName write FColumnName;
79  property ColumnType: TZSQLType read FColumnType write FColumnType;
80  property NewValue: Boolean read FNewValue write FNewValue;
81  property DefaultValue: string read FDefaultValue write FDefaultValue;
82  end;
83 
84  {**
85  Implements a generic cached resolver object which generates
86  DML SQL statements and posts resultset updates to database.
87  }
88 
89  { TZGenericCachedResolver }
90 
91  TZGenericCachedResolver = class (TInterfacedObject, IZCachedResolver)
92  private
93  FConnection: IZConnection;
94  FStatement : IZStatement;
95  FMetadata: IZResultSetMetadata;
96  FDatabaseMetadata: IZDatabaseMetadata;
97  FIdentifierConvertor: IZIdentifierConvertor;
98 
99  FInsertColumns: TObjectList;
100  FUpdateColumns: TObjectList;
101  FWhereColumns: TObjectList;
102 
103  FCalcDefaults: Boolean;
104  FWhereAll: Boolean;
105  FUpdateAll: Boolean;
106 
107  InsertStatement : IZPreparedStatement;
108  UpdateStatement : IZPreparedStatement;
109  DeleteStatement : IZPreparedStatement;
110 
111  protected
112  procedure CopyResolveParameters(FromList, ToList: TObjectList);
113  function ComposeFullTableName(Catalog, Schema, Table: string): string;
114  function DefineTableName: string;
115 
116  function CreateResolverStatement(SQL : String):IZPreparedStatement;
117 
118  procedure DefineCalcColumns(Columns: TObjectList;
119  RowAccessor: TZRowAccessor);
120  procedure DefineInsertColumns(Columns: TObjectList);
121  procedure DefineUpdateColumns(Columns: TObjectList;
122  OldRowAccessor, NewRowAccessor: TZRowAccessor);
123  procedure DefineWhereKeyColumns(Columns: TObjectList);
124  procedure DefineWhereAllColumns(Columns: TObjectList; IgnoreKeyColumn: Boolean = False);
125  function CheckKeyColumn(ColumnIndex: Integer): Boolean; virtual;
126 
127  procedure FillStatement(Statement: IZPreparedStatement;
128  Params: TObjectList; OldRowAccessor, NewRowAccessor: TZRowAccessor);
129 
130  property Connection: IZConnection read FConnection write FConnection;
131  property Metadata: IZResultSetMetadata read FMetadata write FMetadata;
132  property DatabaseMetadata: IZDatabaseMetadata read FDatabaseMetadata
133  write FDatabaseMetadata;
134  property IdentifierConvertor: IZIdentifierConvertor
135  read FIdentifierConvertor write FIdentifierConvertor;
136 
137  property InsertColumns: TObjectList read FInsertColumns;
138  property UpdateColumns: TObjectList read FUpdateColumns;
139  property WhereColumns: TObjectList read FWhereColumns;
140 
141  property CalcDefaults: Boolean read FCalcDefaults write FCalcDefaults;
142  property WhereAll: Boolean read FWhereAll write FWhereAll;
143  property UpdateAll: Boolean read FUpdateAll write FUpdateAll;
144 
145  public
146  constructor Create(Statement: IZStatement; Metadata: IZResultSetMetadata);
147  destructor Destroy; override;
148 
149  function FormWhereClause(Columns: TObjectList;
150  OldRowAccessor: TZRowAccessor): string; virtual;
151  function FormInsertStatement(Columns: TObjectList;
152  NewRowAccessor: TZRowAccessor): string;
153  function FormUpdateStatement(Columns: TObjectList;
154  OldRowAccessor, NewRowAccessor: TZRowAccessor): string;
155  function FormDeleteStatement(Columns: TObjectList;
156  OldRowAccessor: TZRowAccessor): string;
157  function FormCalculateStatement(Columns: TObjectList): string; virtual;
158 
159  procedure CalculateDefaults(Sender: IZCachedResultSet;
160  RowAccessor: TZRowAccessor);
161  procedure PostUpdates(Sender: IZCachedResultSet;
162  UpdateType: TZRowUpdateType;
163  OldRowAccessor, NewRowAccessor: TZRowAccessor); virtual;
164  {BEGIN of PATCH [1185969]: Do tasks after posting updates. ie: Updating AutoInc fields in MySQL }
165  procedure UpdateAutoIncrementFields(Sender: IZCachedResultSet;
166  UpdateType: TZRowUpdateType;
167  OldRowAccessor, NewRowAccessor: TZRowAccessor; Resolver: IZCachedResolver); virtual;
168  {END of PATCH [1185969]: Do tasks after posting updates. ie: Updating AutoInc fields in MySQL }
169  procedure RefreshCurrentRow(Sender: IZCachedResultSet;RowAccessor: TZRowAccessor); //FOS+ 07112006
170 
171  end;
172 
173 implementation
174 
175 uses ZMessages, ZSysUtils, ZDbcMetadata, ZDbcUtils;
176 
177 { TZResolverParameter }
178 
179 {**
180  Constructs this resolver parameter and assignes the main properties.
181  @param ColumnIndex a result set column index.
182  @param ColumnName a result set column name.
183  @param NewValue <code>True</code> for new value and <code>False</code>
184  for an old one.
185  @param DefaultValue a default column value to evalute on server.
186 }
187 constructor TZResolverParameter.Create(ColumnIndex: Integer;
188  ColumnName: string; ColumnType: TZSQLType; NewValue: Boolean; DefaultValue: string);
189 begin
190  FColumnType := ColumnType;
191  FColumnIndex := ColumnIndex;
192  FColumnName := ColumnName;
193  FNewValue := NewValue;
194  FDefaultValue := DefaultValue;
195 end;
196 
197 { TZGenericCachedResolver }
198 
199 {**
200  Creates a cached resolver and assignes the main properties.
201  @param ResultSet a related ResultSet object.
202 }
203 constructor TZGenericCachedResolver.Create(Statement: IZStatement;
204  Metadata: IZResultSetMetadata);
205 begin
206  FStatement := Statement;
207  FConnection := Statement.GetConnection;
208  FMetadata := Metadata;
209  FDatabaseMetadata := Statement.GetConnection.GetMetadata;
210  FIdentifierConvertor := FDatabaseMetadata.GetIdentifierConvertor;
211 
212  FInsertColumns := TObjectList.Create(True);
213  FWhereColumns := TObjectList.Create(True);
214  FUpdateColumns := TObjectList.Create(True);
215 
216  FCalcDefaults := StrToBoolEx(DefineStatementParameter(Statement,
217  'defaults', 'true'));
218  FUpdateAll := UpperCase(DefineStatementParameter(Statement,
219  'update', 'changed')) = 'ALL';
220  FWhereAll := UpperCase(DefineStatementParameter(Statement,
221  'where', 'keyonly')) = 'ALL';
222 
223  InsertStatement := nil;
224  UpdateStatement := nil;
225  DeleteStatement := nil;
226 
227 end;
228 
229 {**
230  Destroys this object and cleanups the memory.
231 }
232 destructor TZGenericCachedResolver.Destroy;
233 begin
234  FMetadata := nil;
235  FDatabaseMetadata := nil;
236 
237  FreeAndNil(FInsertColumns);
238  FreeAndNil(FUpdateColumns);
239  FreeAndNil(FWhereColumns);
240 
241  inherited Destroy;
242 end;
243 
244 {**
245  Copies resolver parameters from source list to destination list.
246  @param FromList the source object list.
247  @param ToList the destination object list.
248 }
249 procedure TZGenericCachedResolver.CopyResolveParameters(
250  FromList: TObjectList; ToList: TObjectList);
251 var
252  I: Integer;
253  Current: TZResolverParameter;
254 begin
255  for I := 0 to FromList.Count - 1 do
256  begin
257  Current := TZResolverParameter(FromList[I]);
258  if Current.ColumnName <> '' then
259  ToList.Add(TZResolverParameter.Create(Current.ColumnIndex,
260  Current.ColumnName, Current.ColumnType, Current.NewValue, ''));
261  end;
262 end;
263 
264 {**
265  Composes a fully quilified table name.
266  @param Catalog a table catalog name.
267  @param Schema a table schema name.
268  @param Table a table name.
269  @return a fully qualified table name.
270 }
271 function TZGenericCachedResolver.ComposeFullTableName(Catalog, Schema,
272  Table: string): string;
273 begin
274  if Table <> '' then
275  begin
276  Result := IdentifierConvertor.Quote(Table);
277  if Schema <> '' then
278  Result := IdentifierConvertor.Quote(Schema) + '.' + Result;
279  if Catalog <> '' then
280  Result := IdentifierConvertor.Quote(Catalog) + '.' + Result;
281  end
282  else
283  Result := '';
284 end;
285 
286 {**
287  Defines a table name from the select statement.
288 }
289 function TZGenericCachedResolver.DefineTableName: string;
290 var
291  I: Integer;
292  Temp: string;
293 begin
294  Result := '';
295  for I := 1 to Metadata.GetColumnCount do
296  begin
297  Temp := ComposeFullTableName(Metadata.GetCatalogName(I),
298  Metadata.GetSchemaName(I), Metadata.GetTableName(I));
299  if (Result = '') and (Temp <> '') then
300  Result := Temp
301  else if (Result <> '') and (Temp <> '') and (Temp <> Result) then
302  raise EZSQLException.Create(SCanNotUpdateComplexQuery);
303  end;
304  if Result = '' then
305  raise EZSQLException.Create(SCanNotUpdateThisQueryType);
306 end;
307 
308 function TZGenericCachedResolver.CreateResolverStatement(SQL: String): IZPreparedStatement;
309 var
310  Temp : TStrings;
311 begin
312  if StrToBoolEx(FStatement.GetParameters.Values['preferprepared']) then
313  begin
314  Temp := TStringList.Create;
315  Temp.Values['preferprepared'] := 'true';
316  if not ( Connection.GetParameters.Values['chunk_size'] = '' ) then //ordered by precedence
317  Temp.Values['chunk_size'] := Connection.GetParameters.Values['chunk_size']
318  else
319  Temp.Values['chunk_size'] := FStatement.GetParameters.Values['chunk_size'];
320  Result := Connection.PrepareStatementWithParams(SQL, Temp);
321  Temp.Free;
322  end
323  else
324  Result := Connection.PrepareStatement(SQL);
325 
326 end;
327 
328 {**
329  Gets a collection of data columns for INSERT statements.
330  @param Columns a collection of columns.
331 }
332 procedure TZGenericCachedResolver.DefineInsertColumns(Columns: TObjectList);
333 var
334  I: Integer;
335 begin
336  { Precache insert parameters. }
337  if InsertColumns.Count = 0 then
338  begin
339  for I := 1 to Metadata.GetColumnCount do
340  begin
341  if (Metadata.GetTableName(I) <> '') and (Metadata.GetColumnName(I) <> '')
342  and Metadata.IsWritable(I) then
343  begin
344  InsertColumns.Add(TZResolverParameter.Create(I,
345  Metadata.GetColumnName(I), Metadata.GetColumnType(I), True, ''));
346  end;
347  end;
348  end;
349  { Use cached insert parameters }
350  CopyResolveParameters(InsertColumns, Columns);
351 end;
352 
353 {**
354  Gets a collection of data columns for UPDATE statements.
355  @param Columns a collection of columns.
356  @param OldRowAccessor an accessor object to old column values.
357  @param NewRowAccessor an accessor object to new column values.
358 }
359 procedure TZGenericCachedResolver.DefineUpdateColumns(
360  Columns: TObjectList; OldRowAccessor, NewRowAccessor: TZRowAccessor);
361 var
362  I: Integer;
363  ColumnIndices: TIntegerDynArray;
364  ColumnDirs: TBooleanDynArray;
365 begin
366  { Use precached parameters. }
367  if UpdateAll and (UpdateColumns.Count > 0) then
368  begin
369  CopyResolveParameters(UpdateColumns, Columns);
370  Exit;
371  end;
372 
373  { Defines parameters for UpdateAll mode. }
374  if UpdateAll then
375  begin
376  for I := 1 to Metadata.GetColumnCount do
377  begin
378  if (Metadata.GetTableName(I) <> '') and (Metadata.GetColumnName(I) <> '')
379  and Metadata.IsWritable(I) then
380  begin
381  UpdateColumns.Add(TZResolverParameter.Create(I,
382  Metadata.GetColumnName(I), Metadata.GetColumnType(I), True, ''));
383  end;
384  end;
385  CopyResolveParameters(UpdateColumns, Columns);
386  end
387  { Defines parameters for UpdateChanged mode. }
388  else
389  begin
390  SetLength(ColumnIndices, 1);
391  SetLength(ColumnDirs, 1);
392  ColumnDirs[0] := True;
393  for I := 1 to Metadata.GetColumnCount do
394  begin
395  ColumnIndices[0] := I;
396  if (Metadata.GetTableName(I) <> '') and (Metadata.GetColumnName(I) <> '')
397  and Metadata.IsWritable(I) and (OldRowAccessor.CompareBuffers(
398  OldRowAccessor.RowBuffer, NewRowAccessor.RowBuffer, ColumnIndices,
399  ColumnDirs) <> 0)then
400  begin
401  Columns.Add(TZResolverParameter.Create(I,
402  Metadata.GetColumnName(I), Metadata.GetColumnType(I), True, ''));
403  end;
404  end;
405  end;
406 end;
407 
408 {**
409  Gets a collection of where key columns for DELETE or UPDATE DML statements.
410  @param Columns a collection of key columns.
411 }
412 procedure TZGenericCachedResolver.DefineWhereKeyColumns(Columns: TObjectList);
413 var
414  I: Integer;
415  Found: Boolean;
416  ColumnName: string;
417  Catalog, Schema, Table: string;
418  PrimaryKeys: IZResultSet;
419 begin
420  { Use precached values. }
421  if WhereColumns.Count > 0 then
422  begin
423  CopyResolveParameters(WhereColumns, Columns);
424  Exit;
425  end;
426 
427  { Defines catalog, schema and a table. }
428  Table := DefineTableName;
429  for I := 1 to Metadata.GetColumnCount do
430  begin
431  Table := Metadata.GetTableName(I);
432  if Table <> '' then
433  begin
434  Schema := Metadata.GetSchemaName(I);
435  Catalog := Metadata.GetCatalogName(I);
436  Break;
437  end;
438  end;
439 
440  { Tryes to define primary keys. }
441  if not WhereAll then
442  begin
443  {For exact results: quote all identifiers SEE: http://sourceforge.net/p/zeoslib/tickets/81/
444  If table names have mixed case ConstructNameCondition will return wrong results
445  and we fall back to WhereAll}
446  PrimaryKeys := DatabaseMetadata.GetPrimaryKeys(IdentifierConvertor.Quote(Catalog),
447  IdentifierConvertor.Quote(Schema), IdentifierConvertor.Quote(Table));
448  while PrimaryKeys.Next do
449  begin
450  ColumnName := PrimaryKeys.GetString(4);
451  Found := False;
452  for I := 1 to Metadata.GetColumnCount do
453  begin
454  if (ColumnName = Metadata.GetColumnName(I))
455  and (Table = Metadata.GetTableName(I)) then
456  begin
457  Found := True;
458  Break;
459  end;
460  end;
461  if not Found then
462  begin
463  WhereColumns.Clear;
464  Break;
465  end;
466  WhereColumns.Add(TZResolverParameter.Create(I, ColumnName,
467  stUnknown, False, ''));
468  end;
469  end;
470 
471  if WhereColumns.Count > 0 then
472  CopyResolveParameters(WhereColumns, Columns)
473  else
474  DefineWhereAllColumns(Columns);
475 end;
476 
477 {**
478  Gets a collection of where all columns for DELETE or UPDATE DML statements.
479  @param Columns a collection of key columns.
480 }
481 procedure TZGenericCachedResolver.DefineWhereAllColumns(Columns: TObjectList;
482  IgnoreKeyColumn: Boolean = False);
483 var
484  I: Integer;
485 begin
486  { Use precached values. }
487  if WhereColumns.Count > 0 then
488  begin
489  CopyResolveParameters(WhereColumns, Columns);
490  Exit;
491  end;
492 
493  { Takes a a key all non-blob fields. }
494  for I := 1 to Metadata.GetColumnCount do
495  begin
496  if CheckKeyColumn(I) then
497  WhereColumns.Add(TZResolverParameter.Create(I,
498  Metadata.GetColumnName(I), Metadata.GetColumnType(I), False, ''))
499  else
500  if IgnoreKeyColumn then
501  WhereColumns.Add(TZResolverParameter.Create(I,
502  Metadata.GetColumnName(I), Metadata.GetColumnType(I), False, ''));
503  end;
504  if ( WhereColumns.Count = 0 ) and ( not IgnoreKeyColumn ) then
505  DefineWhereAllColumns(Columns, True)
506  else
507  { Copy defined parameters to target columns }
508  CopyResolveParameters(WhereColumns, Columns);
509 end;
510 
511 {**
512  Checks is the specified column can be used in where clause.
513  @param ColumnIndex an index of the column.
514  @returns <code>true</code> if column can be included into where clause.
515 }
516 function TZGenericCachedResolver.CheckKeyColumn(ColumnIndex: Integer): Boolean;
517 begin
518  Result := (Metadata.GetTableName(ColumnIndex) <> '')
519  and (Metadata.GetColumnName(ColumnIndex) <> '')
520  and Metadata.IsSearchable(ColumnIndex)
521  and not (Metadata.GetColumnType(ColumnIndex)
522  in [stUnknown, stAsciiStream, stBinaryStream, stUnicodeStream]);
523 end;
524 
525 {**
526  Gets a collection of data columns to initialize before INSERT statements.
527  @param Columns a collection of columns.
528  @param RowAccessor an accessor object to column values.
529 }
530 procedure TZGenericCachedResolver.DefineCalcColumns(Columns: TObjectList;
531  RowAccessor: TZRowAccessor);
532 var
533  I: Integer;
534 begin
535  for I := 1 to Metadata.GetColumnCount do
536  begin
537  if RowAccessor.IsNull(I) and (Metadata.GetTableName(I) <> '')
538  and ((Metadata.GetDefaultValue(I) <> '') or (RowAccessor.GetColumnDefaultExpression(I) <> '')) then
539  begin
540  // DefaultExpression takes takes precedence on database default value
541  if RowAccessor.GetColumnDefaultExpression(I) <> '' then
542  Columns.Add(TZResolverParameter.Create(I,
543  Metadata.GetColumnName(I), Metadata.GetColumnType(I),
544  True, RowAccessor.GetColumnDefaultExpression(I)))
545  else
546  Columns.Add(TZResolverParameter.Create(I,
547  Metadata.GetColumnName(I), Metadata.GetColumnType(I),
548  True, Metadata.GetDefaultValue(I)));
549  end;
550  end;
551 end;
552 
553 {**
554  Fills the specified statement with stored or given parameters.
555  @param ResultSet a source result set object.
556  @param Statement a DBC statement object.
557  @param Config an UpdateStatement configuration.
558  @param OldRowAccessor an accessor object to old column values.
559  @param NewRowAccessor an accessor object to new column values.
560 }
561 procedure TZGenericCachedResolver.FillStatement(Statement: IZPreparedStatement;
562  Params: TObjectList; OldRowAccessor, NewRowAccessor: TZRowAccessor);
563 var
564  I: Integer;
565  ColumnIndex: Integer;
566  Current: TZResolverParameter;
567  RowAccessor: TZRowAccessor;
568  WasNull: Boolean;
569 begin
570  WasNull := False;
571  for I := 0 to Params.Count - 1 do
572  begin
573  Current := TZResolverParameter(Params[I]);
574  if Current.NewValue then
575  RowAccessor := NewRowAccessor
576  else
577  RowAccessor := OldRowAccessor;
578  ColumnIndex := Current.ColumnIndex;
579 
580  if FCalcDefaults then
581  Statement.SetDefaultValue(I + 1, Metadata.GetDefaultValue(ColumnIndex));
582 
583  case Metadata.GetColumnType(ColumnIndex) of
584  stBoolean:
585  Statement.SetBoolean(I + 1,
586  RowAccessor.GetBoolean(ColumnIndex, WasNull));
587  stByte:
588  Statement.SetByte(I + 1, RowAccessor.GetByte(ColumnIndex, WasNull));
589  stShort:
590  Statement.SetShort(I + 1, RowAccessor.GetShort(ColumnIndex, WasNull));
591  stInteger:
592  Statement.SetInt(I + 1, RowAccessor.GetInt(ColumnIndex, WasNull));
593  stLong:
594  Statement.SetLong(I + 1, RowAccessor.GetLong(ColumnIndex, WasNull));
595  stFloat:
596  Statement.SetFloat(I + 1, RowAccessor.GetFloat(ColumnIndex, WasNull));
597  stDouble:
598  Statement.SetDouble(I + 1, RowAccessor.GetDouble(ColumnIndex, WasNull));
599  stBigDecimal:
600  Statement.SetBigDecimal(I + 1,
601  RowAccessor.GetBigDecimal(ColumnIndex, WasNull));
602  stString:
603  Statement.SetString(I + 1, RowAccessor.GetString(ColumnIndex, WasNull));
604  stUnicodeString:
605  Statement.SetUnicodeString(I + 1,
606  RowAccessor.GetUnicodeString(ColumnIndex, WasNull));
607  stBytes, stGUID:
608  Statement.SetBytes(I + 1, RowAccessor.GetBytes(ColumnIndex, WasNull));
609  stDate:
610  Statement.SetDate(I + 1, RowAccessor.GetDate(ColumnIndex, WasNull));
611  stTime:
612  Statement.SetTime(I + 1, RowAccessor.GetTime(ColumnIndex, WasNull));
613  stTimestamp:
614  Statement.SetTimestamp(I + 1,
615  RowAccessor.GetTimestamp(ColumnIndex, WasNull));
616  stAsciiStream:
617  Statement.SetBlob(I + 1, stAsciiStream,
618  RowAccessor.GetBlob(ColumnIndex, WasNull));
619  stUnicodeStream:
620  Statement.SetBlob(I + 1, stUnicodeStream,
621  RowAccessor.GetBlob(ColumnIndex, WasNull));
622  stBinaryStream:
623  Statement.SetBlob(I + 1, stBinaryStream,
624  RowAccessor.GetBlob(ColumnIndex, WasNull));
625  end;
626  if WasNull then
627  Statement.SetNull(I + 1, Metadata.GetColumnType(ColumnIndex))
628  end;
629 end;
630 
631 {**
632  Forms a where clause for UPDATE or DELETE DML statements.
633  @param Columns a collection of key columns.
634  @param OldRowAccessor an accessor object to old column values.
635 }
636 function TZGenericCachedResolver.FormWhereClause(Columns: TObjectList;
637  OldRowAccessor: TZRowAccessor): string;
638 var
639  I, N: Integer;
640  Current: TZResolverParameter;
641 begin
642  Result := '';
643  N := Columns.Count - WhereColumns.Count;
644 
645  for I := 0 to WhereColumns.Count - 1 do
646  begin
647  Current := TZResolverParameter(WhereColumns[I]);
648  if Result <> '' then
649  Result := Result + ' AND ';
650 
651  Result := Result + IdentifierConvertor.Quote(Current.ColumnName);
652  if OldRowAccessor.IsNull(Current.ColumnIndex) then
653  begin
654  Result := Result + ' IS NULL ';
655  Columns.Delete(N);
656  end
657  else
658  begin
659  Result := Result + '=?';
660  Inc(N);
661  end;
662  end;
663 
664  if Result <> '' then
665  Result := ' WHERE ' + Result;
666 end;
667 
668 {**
669  Forms a where clause for INSERT statements.
670  @param Columns a collection of key columns.
671  @param NewRowAccessor an accessor object to new column values.
672 }
673 function TZGenericCachedResolver.FormInsertStatement(Columns: TObjectList;
674  NewRowAccessor: TZRowAccessor): string;
675 var
676  I: Integer;
677  Current: TZResolverParameter;
678  TableName: string;
679  Temp1, Temp2: string;
680  l1: Integer;
681 
682  procedure Append(const app: String);
683  begin
684  if Length(Temp1) < l1 + length(app) then
685  SetLength(Temp1, 2 * (length(app) + l1));
686  Move(app[1], Temp1[l1+1], length(app)*SizeOf(Char));
687  Inc(l1, length(app));
688  end;
689 
690 begin
691  TableName := DefineTableName;
692  DefineInsertColumns(Columns);
693  if Columns.Count = 0 then
694  begin
695  Result := '';
696  Exit;
697  end;
698 
699  Temp1 := ''; l1 := 0;
700  SetLength(Temp2, 2 * Columns.Count - 1);
701  for I := 0 to Columns.Count - 1 do
702  begin
703  Current := TZResolverParameter(Columns[I]);
704  if Temp1 <> '' then
705  Append(',');
706  Append(IdentifierConvertor.Quote(Current.ColumnName));
707  if I > 0 then
708  Temp2[I*2] := ',';
709  Temp2[I*2+1] := '?';
710  end;
711  SetLength(Temp1, l1);
712  Result := Format('INSERT INTO %s (%s) VALUES (%s)', [TableName, Temp1, Temp2]);
713 end;
714 
715 {**
716  Forms a where clause for UPDATE statements.
717  @param Columns a collection of key columns.
718  @param OldRowAccessor an accessor object to old column values.
719  @param NewRowAccessor an accessor object to new column values.
720 }
721 function TZGenericCachedResolver.FormUpdateStatement(Columns: TObjectList;
722  OldRowAccessor, NewRowAccessor: TZRowAccessor): string;
723 var
724  I: Integer;
725  Current: TZResolverParameter;
726  TableName: string;
727  Temp: string;
728 begin
729  TableName := DefineTableName;
730  DefineUpdateColumns(Columns, OldRowAccessor, NewRowAccessor);
731  if Columns.Count = 0 then
732  begin
733  Result := '';
734  Exit;
735  end;
736 
737  Temp := '';
738  for I := 0 to Columns.Count - 1 do
739  begin
740  Current := TZResolverParameter(Columns[I]);
741  if Temp <> '' then
742  Temp := Temp + ',';
743  Temp := Temp + IdentifierConvertor.Quote(Current.ColumnName) + '=?';
744  end;
745 
746  Result := Format('UPDATE %s SET %s', [TableName, Temp]);
747  DefineWhereKeyColumns(Columns);
748  Result := Result + FormWhereClause(Columns, OldRowAccessor);
749 end;
750 
751 {**
752  Forms a where clause for DELETE statements.
753  @param Columns a collection of key columns.
754  @param OldRowAccessor an accessor object to old column values.
755 }
756 function TZGenericCachedResolver.FormDeleteStatement(Columns: TObjectList;
757  OldRowAccessor: TZRowAccessor): string;
758 var
759  TableName: string;
760 begin
761  TableName := DefineTableName;
762  Result := Format('DELETE FROM %s', [TableName]);
763  DefineWhereKeyColumns(Columns);
764  Result := Result + FormWhereClause(Columns, OldRowAccessor);
765 end;
766 
767 {**
768  Forms a where clause for SELECT statements to calculate default values.
769  @param Columns a collection of key columns.
770  @param OldRowAccessor an accessor object to old column values.
771 }
772 function TZGenericCachedResolver.FormCalculateStatement(
773  Columns: TObjectList): string;
774 var
775  I: Integer;
776  Current: TZResolverParameter;
777 begin
778  Result := '';
779  if Columns.Count = 0 then
780  Exit;
781 
782  for I := 0 to Columns.Count - 1 do
783  begin
784  Current := TZResolverParameter(Columns[I]);
785  if Result <> '' then
786  Result := Result + ',';
787  if Current.DefaultValue <> '' then
788  Result := Result + Current.DefaultValue
789  else
790  Result := Result + 'NULL';
791  end;
792  Result := 'SELECT ' + Result;
793 end;
794 
795 {**
796  Posts updates to database.
797  @param Sender a cached result set object.
798  @param UpdateType a type of updates.
799  @param OldRowAccessor an accessor object to old column values.
800  @param NewRowAccessor an accessor object to new column values.
801 }
802 procedure TZGenericCachedResolver.PostUpdates(Sender: IZCachedResultSet;
803  UpdateType: TZRowUpdateType; OldRowAccessor, NewRowAccessor: TZRowAccessor);
804 var
805  Statement : IZPreparedStatement;
806  SQL : string;
807  SQLParams : TObjectList;
808  lUpdateCount : Integer;
809  lValidateUpdateCount : Boolean;
810 
811 begin
812  if (UpdateType = utDeleted)
813  and (OldRowAccessor.RowBuffer.UpdateType = utInserted) then
814  Exit;
815 
816  SQLParams := TObjectList.Create(True);
817  try
818  case UpdateType of
819  utInserted:
820  begin
821  SQL := FormInsertStatement(SQLParams, NewRowAccessor);
822  If Assigned(InsertStatement) and (SQL <> InsertStatement.GetSQL) then
823  InsertStatement := nil;
824  If not Assigned(InsertStatement) then
825  InsertStatement := CreateResolverStatement(SQL);
826  Statement := InsertStatement;
827  end;
828  utDeleted:
829  begin
830  SQL := FormDeleteStatement(SQLParams, OldRowAccessor);
831  If Assigned(DeleteStatement) and (SQL <> DeleteStatement.GetSQL) then
832  DeleteStatement := nil;
833  If not Assigned(DeleteStatement) then
834  DeleteStatement := CreateResolverStatement(SQL);
835  Statement := DeleteStatement;
836  end;
837  utModified:
838  begin
839  SQL := FormUpdateStatement(SQLParams, OldRowAccessor, NewRowAccessor);
840  If SQL =''then // no fields have been changed
841  exit;
842  If Assigned(UpdateStatement) and (SQL <> UpdateStatement.GetSQL) then
843  UpdateStatement := nil;
844  If not Assigned(UpdateStatement) then
845  UpdateStatement := CreateResolverStatement(SQL);
846  Statement := UpdateStatement;
847  end;
848  else
849  Exit;
850  end;
851 
852  if SQL <> '' then
853  begin
854 
855  FillStatement(Statement, SQLParams, OldRowAccessor, NewRowAccessor);
856  // if Property ValidateUpdateCount isn't set : assume it's true
857  lValidateUpdateCount := (Sender.GetStatement.GetParameters.IndexOfName('ValidateUpdateCount') = -1)
858  or StrToBoolEx(Sender.GetStatement.GetParameters.Values['ValidateUpdateCount']);
859 
860  lUpdateCount := Statement.ExecuteUpdatePrepared;
861  {$IFDEF WITH_VALIDATE_UPDATE_COUNT}
862  if (lValidateUpdateCount) and (lUpdateCount <> 1 ) then
863  raise EZSQLException.Create(Format(SInvalidUpdateCount, [lUpdateCount]));
864  {$ENDIF}
865  end;
866  finally
867  FreeAndNil(SQLParams);
868  end;
869 end;
870 
871 procedure TZGenericCachedResolver.RefreshCurrentRow(Sender: IZCachedResultSet; RowAccessor: TZRowAccessor);
872 begin
873  raise EZSQLException.Create(SRefreshRowOnlySupportedWithUpdateObject);
874 end;
875 
876 {**
877  Calculate default values for the fields.
878  @param Sender a cached result set object.
879  @param RowAccessor an accessor object to column values.
880 }
881 procedure TZGenericCachedResolver.CalculateDefaults(
882  Sender: IZCachedResultSet; RowAccessor: TZRowAccessor);
883 var
884  I: Integer;
885  SQL: string;
886  SQLParams: TObjectList;
887  Statement: IZStatement;
888  ResultSet: IZResultSet;
889  Metadata: IZResultSetMetadata;
890  Current: TZResolverParameter;
891 begin
892  if not FCalcDefaults then
893  Exit;
894 
895  SQLParams := TObjectList.Create(True);
896  try
897  DefineCalcColumns(SQLParams, RowAccessor);
898  SQL := FormCalculateStatement(SQLParams);
899  if SQL = '' then
900  Exit;
901 
902  { Executes statement and fills default fields. }
903  Statement := Connection.CreateStatement;
904  try
905  ResultSet := Statement.ExecuteQuery(SQL);
906  if ResultSet.Next then
907  begin
908  Metadata := ResultSet.GetMetadata;
909  for I := 1 to Metadata.GetColumnCount do
910  begin
911  Current := TZResolverParameter(SQLParams[I - 1]);
912  try
913  case Current.ColumnType of
914  stBoolean:
915  RowAccessor.SetBoolean(Current.ColumnIndex,
916  ResultSet.GetBoolean(I));
917  stByte:
918  RowAccessor.SetByte(Current.ColumnIndex, ResultSet.GetByte(I));
919  stShort:
920  RowAccessor.SetShort(Current.ColumnIndex, ResultSet.GetShort(I));
921  stInteger:
922  RowAccessor.SetInt(Current.ColumnIndex, ResultSet.GetInt(I));
923  stLong:
924  RowAccessor.SetLong(Current.ColumnIndex, ResultSet.GetLong(I));
925  stFloat:
926  RowAccessor.SetFloat(Current.ColumnIndex, ResultSet.GetFloat(I));
927  stDouble:
928  RowAccessor.SetDouble(Current.ColumnIndex, ResultSet.GetDouble(I));
929  stBigDecimal:
930  RowAccessor.SetBigDecimal(Current.ColumnIndex, ResultSet.GetBigDecimal(I));
931  stString, stAsciiStream:
932  RowAccessor.SetString(Current.ColumnIndex, ResultSet.GetString(I));
933  stUnicodeString, stUnicodeStream:
934  RowAccessor.SetUnicodeString(Current.ColumnIndex, ResultSet.GetUnicodeString(I));
935  stBytes, stGUID:
936  RowAccessor.SetBytes(Current.ColumnIndex, ResultSet.GetBytes(I));
937  stDate:
938  RowAccessor.SetDate(Current.ColumnIndex, ResultSet.GetDate(I));
939  stTime:
940  RowAccessor.SetTime(Current.ColumnIndex, ResultSet.GetTime(I));
941  stTimestamp:
942  RowAccessor.SetTimestamp(Current.ColumnIndex,
943  ResultSet.GetTimestamp(I));
944  end;
945 
946  if ResultSet.WasNull then
947  RowAccessor.SetNull(Current.ColumnIndex);
948  except
949  { Supress any errors in default fields. }
950  end;
951  end;
952  end;
953  ResultSet.Close;
954  finally
955  Statement.Close;
956  end;
957  finally
958  FreeAndNil(SQLParams);
959  end;
960 end;
961 
962 {BEGIN of PATCH [1185969]: Do tasks after posting updates. ie: Updating AutoInc fields in MySQL }
963 procedure TZGenericCachedResolver.UpdateAutoIncrementFields(
964  Sender: IZCachedResultSet; UpdateType: TZRowUpdateType; OldRowAccessor,
965  NewRowAccessor: TZRowAccessor; Resolver: IZCachedResolver);
966 begin
967  //Should be implemented at Specific database Level Cached resolver
968 end;
969 {END of PATCH [1185969]: Do tasks after posting updates. ie: Updating AutoInc fields in MySQL }
970 
971 end.
972